                            DINKC Reference v3.1

           Original work: Seth Able Robinson  sethable@rtsoft.com
      [bracketed insertions and corrections: Ted Shutes, theo@ia4u.net]

[Please note: this is Seth Robinson's DinkC.txt, the one that appears in the
"develop" directory of every copy of Dink Smallwood, except that this version
is in the process of being FREELY modified by yours truly as I learn DinkC
programming.  I gave it an arbitrary version number as a point of reference:
I still update it almost every day I work on DinkC stuff, so I may have a
better version to upload from time to time.
   All text in square brackets, as is this text, has been inserted by me, Ted
Shutes, theo@ia4u.net.  If I have included inaccuracies, or you have answers
to some of my contained, pending questions, or you have additional insights
on the various DinkC commands, feel free to let me know.  All input is
appreciated.

   Special thanks to Paul "ManaUser" Pliska.  He had thought to do something
like this but apparently I beat him to it.  He has provided a *huge* amount
of input for this document.

   Much more good input from these contributors: Tatiana "Tyrsis" Ryzhova,
Sylvain Beucler, and Dan "Redink1" Walma, new to this version.

   Also new in this version: "Appendix A: System Variables" (incomplete),
in addition to the usual bunch of corrections and new insights.

                  ----------------------------------------

[Seth's opening remarks:]

** What's new in 1.06? (10-19-99) ** 

Only one feature has been added.  You can attach a script to nearly any key
on the keyboard now.  This way, you can have [the "A" key] create an apple
and [the "+" key] make something bigger or whatever.  Inside of source.zip
there is a file called key-80.c.  If this exists in the story dir, pressing
key 80 [the "P" key] will run this script.  (It's actually in Dink, too.
Try pressing P [and] you should hear a pillbug squeal in delight.)

[Thanks, Seth!  This is, among other things, an excellent programming tool.
I create a key-45.c that responds to the Insert key and use it to have Dink
tell me ("say") the current status of any variables I am interested in as
I work through a dmod.  Any changes I make to the script from another window
are immediately reflected the next time I press the Insert key; so I can use
it to make one-time corrections, or to test how commands work, or any script-
related changes I want to make to the game, and they can be run the moment I
save and Alt+Tab back to Dink.]

Keys Dink uses internally such as Shift, Enter, Ctrl, etc., are not usable.
[Apparently, Backspace is part of this set.] F1-F12 and the numpad are [okay]
though!  Hit Alt-D to get into debug mode while testing your DMOD and it will
show you the numeric value for any key you hit.

To recap:

If a file called  "key-(any number here).c" exists in the story dir, Dink
will load it and run the main() procedure in it.  Don't forget to call
kill_this_task(); when done, or the script will stay in memory...

[Dan Walma's list of keycodes: http://rpgplanet.com/dink/, then follow the
"Development" and "Keycodes" links.  Thanks, Dan!  I can't believe Seth didn't
include it as an appendix to this document; but then again, I haven't either...

"ManaUser" adds this note: "One warning though, these keys may be activated
at random if you ALT+TAB out of Dink and switch back. The only key I've found
(though I haven't made an exhaustive search) that this never happens to is F12
(key-123.c)"
   Good point, though it's not really random.  It would appear that virtually
ALL text keys pressed while Alt-Tabbed out of Dink are passed back to Dink when
you go back in.  I have no idea what to do about it (yet), other than a couple
of workarounds: (1) for script writers, make sure any key-##.c scripts do
nothing that can't be easily reversed unless you select/confirm from a Choice

menu -- fortunately, the CTRL key does seem to be exempt from this weirdness.
And (2), for everybody: make sure you press Enter to go into Inventory before
Alt-Tabbing out of Dink.  Inventory seems to "eat" more keystrokes without
trying to process them.]

** preface **

For info on DINKEDIT.EXE, check TUT1.TXT, [which] is a tutorial on how to use
[DinkEdit.exe] to create your own addon/adventure.

This file [DinkC.txt] is an attempt to give detailed information on every
DinkC command.

Creating DINK addons isn't for beginners.  At least some programming experience
is required, hopefully in C.  Although studying included .C files might be
enough...

[Knowledge of C is slightly helpful but not required.  In fact, C programmers
are going to be more frustrated than prepared for DinkC.  If you have ever
programmed in any scripted or compiled language such as BASIC or Pascal, you
will probably be able to pick up DinkC as easily as a C programmer.  And if
you have never written a program but want to try your hand, well, DinkC is a
fun way to get started.]

In any case, I hope to continue to improve the system and language and offer
tutorials and other help files to make creating new worlds for Dink an easier
process.

Please visit http://www.rtsoft.com to find the latest Dink info and development
tools/info.  [Also www.dinksmallwood.net ( http://rpgplanet.com/dink/ ),
Dan Walma's superb "Dink Network" site.]

If you have any questions or comments, feel free to email me.  Mime-encoded
file attachments are ok if you'd like me to check out something you are doing.
Unless I get totally busy I'll have time for everyone.

I really enjoy helping and I believe that the more creative minds that use
this system to bring their own story and characters to life, the better.

Thanks,

Seth Able Robinson
email: sethable@rtsoft.com


  Part 1:  DinkC - What Is It?
  Part 2:  List of Internal Procedures
 [Part 2A: "Commands To Get/Change Sprite Attributes"]
 [Part 2B: "About PSEQ and PFRAME"]
  Part 3:  A Changing World
  Part 4:  How Items Work
  Part 5:  How the Choice Statement Works
  Part 6:  Overview of Script Execution
  Part 7:  Known limitations
  Appendices: System Variables; example warp procedure


                       -= PART1: DINKC - WHAT IS IT? =-

Ok, it is a scripting language.  I created a similar language for a door game I
wrote called LORD2, and I got a LOT of email about what people liked and didn't
like so I made this one better.  A lot better.

DinkC supports:

  * The format is in standard C/C++ for the most part.  [ahem... let's say
    there's a superficial resemblance to C/C++ and leave it at that, okay?]
  * Nested loops thousands of levels deep [but there are no looping commands!
    "For" and "do while" don't exist!  He must mean that "if" statements can
    be nested to any desired level.]
  * Procedural structure support, limited only by stack [and severely limiting
    programming considerations...]
  * Both local and global dynamic variable creation with custom names. All
    GLOBALS you create are saved with the player data file automatically.
  * Attaching a DinkC script to an object, person or monster - giving [it its]
    own brain.
  * Up to 200 DinkC scripts can be running AT THE SAME TIME [sort of -- the
    dink engine actually only runs one at a time, but it is possible that as
    many as 199 others could be in memory waiting their turn for the engine's
    attention to continue running where they left off.  For those that
    understand the terms, the scheme is more like queued tasks than true
    multi-tasking.]
  * Memory for scripts and other things are dynamically created and destroyed
    continuously.  Blank spaces in the .C file will not waste space.
  * Powerful callback functions to tell Dink when to run your script.
    [set_callback_random() is the only function I know of in this category,
    and it's more dangerous than "powerful."]
  * If an object has a script associated with it (done in DinkEdit [or by
    another script]), if that object is hit, talked to, killed, etc., it will
    automatically look in its script for a hit(), die(), attack() or talk()
    procedure [among several more].
  * Run with the /DEBUG command-line option, Dink.exe will report all
    errors/debug strings in DEBUG.TXT.  [this is such a useless debugging
    tool that I rarely use it and never have gotten useful results from it.]
  * When "compiled" with compile.exe, file gets smaller and encrypted.


*** THE COMMANDS ***

When a DinkC file is associated with a sprite, a function called main() is
loaded and run when the screen is loaded. [Meaning that all main() procedures
in all scripts attached to all sprites on a screen are run when the screen is
loaded!]  This is how you establish what procedures will be called when.
["how" is by attaching a script to a sprite, "what" is which script you choose
to attach, and "when" is when a screen is loaded, usually when Dink walks onto
it, and when certain events happen to a sprite, such as Dink talking to it or
hitting it.  There are a several additional considerations regarding when
scripts are run -- see "Part 6: Overview of Script Execution."]

Map screens can also be associated with a [script, the main() procedure of
which will be] run before the screen is drawn and the sprites are created.
(&current_sprite and sp() don't work yet, because no sprites exist!) [unless
you do a wait(1) first -- again, see "Part 6: Overview of Script Execution"]

Even though the syntax is [nominally] C/C++, there are many differences, most
are to make [the dink engine less complex], such as all variable names must
start with &, but you can put a variable name in the middle of any string and
it will be deciphered correctly.

For instance:

  Say("You have &life life points, Dink.", &current_sprite);

might make the NPC say:

  "You have 15 life points, Dink."

This is true of all variables, no matter what type.

[IMPORTANT, PLEASE READ!  There is an implied limitation on names here, and
it extends to variable names in all contexts, not just those inside quoted
strings: You risk trouble if you name a variable with another variable's name
plus a suffix.  For example, you can have a &wizard1, &wizard2, and &wizard3;
but if you also have a &wizard, then all references to all four variables can
become references to &wizard, regardless of which variables are global and
which are local (as long as the locals are in scope).  This is not a problem
in any other programming language I know, certainly not in C, and can be the
source of some *seriously* insidious bugs.
  (This limitation trips me up most often with names like &goldkey or
&goldcoin -- any name beginning with &gold...)
   See "Variable Naming Rules" for more information.]



                 -=  PART 2: LIST OF INTERNAL PROCEDURES  =-

[Major Revision Note from Ted:

   In Seth's original document, and in previous releases of my revised
   versions, roughly half of the command definitions were arranged almost
   randomly at the start of this document, and the other half were arranged
   under subheadings provided by Seth.

   The only way to look up specific information was the "find" facility of
   whatever software you use to read this monster (I use MetaPad). While that
   it valid enough for occasionally finding an obscure reference somewhere,
   it is a cumbersome method to use all the time: no substitute for a more
   intuitive layout.
      (At ManaUser's suggestion, I have even enhanced the ability to use
   Find: to locate main entry for any command, precede it with a "#".)

   So here's the deal, imposed with great trepidation:
   (1) I have taken all the major command-description entries and organized
       them in ALPHABETICAL ORDER later in this document.  If you wish,
       search for the string "** Alphabetical List of Commands **" to find
       that section.  And
   (2) I have added a "category" entry to each command's definition, telling
       what category or categories that command falls into.

   The categories refer back to this section, where I have collected general
   notes on the commands in each of the categories I have defined so far
   without going into too many details on specific commands.  These, too,
   are discussed in alphabetical order...]


    **********************************************************************
   *************************  Command Categories  *************************
    **********************************************************************


Animation

  [These commands seem to pertain to Seth's "** ABOUT PSEQ and PFRAME **"
  notes -- see "Part 2B: "About PSEQ and PFRAME".]

  Commands in the "Animation" category:
    preload_seq()     (load graphics into cache)
    sp_base_attack()  (base value from which to derive attack sequences)
    sp_base_death()   (base value from which to derive sprite's death graphic)
    sp_base_idle()    (base value from which to derive standing-still seq's)
    sp_base_walk()    (base value from which to derive walking sequences)
    sp_brain()        (basic control of animation sequences)
    sp_frame()        (change the next frame to be shown in an animation)
    sp_frame_delay()  (set/detect animation speed)
    sp_nocontrol()    (freeze sprite until an animation is played)
    sp_pframe()       (set/detect frame part of sprite's graphic)
    sp_picfreeze()    (unknown)
    sp_pseq()         (set/detect sequence part of sprite's graphic)
    sp_reverse()      (cause animation to run in reverse)
    sp_seq()          (set/detect animation sequence)


Basic Syntax

  [In true C/C++, there is a strong distinction between "reserved words",
  which are recognized and interpreted by the compiler, and functions, which
  the compiler itself supports only to the extent of setting up the calls to.
  (At least this is usually true -- there are exceptions, but they are hardly
  the topic of this document.)

  DinkC, on the other hand, has no true "library" functions.  Keywords such
  as "goto" and function-like commands such as say() are all processed and
  supported directly by the dink engine.

  Nevertheless, entries in this category are more like compiler keywords
  than library functions.]

  Commands in the "Basic Syntax" category: goto, if, int, "my_proc()", return


Cut Scene

  [Seth uses the term "cut scene" to refer to scenes where the action proceeds
  automatically, outside of the player's control.  Any command may be and often
  is used in a "cut scene" in some dmod or other, but the commands assigned to
  this category seem, to me at least, particularly intended for this type of
  script.

  Note that scripted warps and virtually all "conversations" with or between
  NPC's are simple cut scenes, so the "Screen Drawing" and "Text Sprite"
  categories should be considered subcategories of this one.]

  Commands in the "Cut Scene" category:
    copy_bmp_to_screen()  (replace screen background with bitmap)
    create_sprite()       (dynamically create a sprite)
    dink_can_walk_off_screen()  (scene can continue after Dink leaves)
    draw_hard_map()       (recalculate hardness after modifying sprites)
    draw_hard_sprite()    (recalculate hardness after modifying one sprite)
    draw_screen()         (draw last screen loaded)
    draw_status()         (draw or re-draw status area and sidebars)
    fade_down()           (fade-to-black effect)
    fade_up()             (undo fade_down())
    fill_screen()         (fill background & status areas with a single color)
    force_vision()        (change vision during a cut scene)
    freeze()              (stop Dink or another sprite from moving)
    load_screen()         (prepare to draw a screen but don't draw it)
    move()                (move sprite to a new location on the screen)
    move_stop()           (move sprite while pausing script)
    unfreeze()            (cancel the effect of freeze())
    wait()                (pause this script, allow others to run)


Dink Specific

  [A few commands pertain to Dink (sprite 1) only. Some, such as push_active(),
  probably could have been implemented as variables rather than commands.]

  Commands in the "Dink Specific" category:
    add_exp()                   (add to &exp "visually correct")
    dink_can_walk_off_screen()  (scene can continue after Dink leaves)
    push_active()               (is Dink allowed to push?)
    set_dink_speed()            (set speed Dink can move)
    sp_brain()                  (brains 1 and 13 are for sprite 1 only)

  (see also "Weapon" category, which mostly applies to Dink's weapons)


Editor Data

  [See "Part 3: A Changing World"]

  Commands in the "Editor Data" category:
    editor_frame()   (specify replacement frame for sprite's graphic)
    editor_seq()     (specify replacement seq. for sprite's graphic)
    editor_type()    (specify how sprite is to be loaded from map.dat)
    sp()             (get DinkEdit sprite # for dink engine sprite #)
    sp_editor_num()  (get dink engine sprite # for DinkEdit sprite #)


Enemy Sprite

  [These commands are primarily used only in scripts that control monsters,
  bosses, and other enemies Dink has to fight.  (Seth calls these scripts
  "brains", but that term quickly becomes confusing with the "brains" built
  into the dink engine and set by sp_brain() or in DinkEdit.)]

  Commands in the "Enemy Sprite" category:
    screenlock()            (prevent Dink from leaving the screen)
    set_callback_random()   (repeatedly call another procedure in this script)
    sp_attack_wait()        (delay a monster's attacking for this long)
    sp_base_attack()        (set attack sequences so enemy will attack)
    sp_base_death()         (base value for sprite's death graphic)
    sp_brain()              (select enemy's basic motion)
    sp_distance()           (how close Dink must get before monster attacks)
    sp_exp()                (set experience awarded to Dink if he kill sprite)
    sp_range()              (specify how far an enemy's attack reaches)
    sp_target()             (specify monster's attack target)
    sp_touch_damage()       (set action caused by Dink touching sprite)


Inventory

  [These deal with weapons and magic that may be added to Dink's pack.  Most
  are discussed in "Part 4: How Items Work".]

  Commands in the "Inventory" category:
    add_item()          (add an item to the weapons area of Dink's pack)
    add_magic()         (add an item to the magic area of Dink's pack)
    arm_magic()         (arm the spell in pack slot &cur_magic)
    arm_weapon()        (arm the weapon in pack slot &cur_weapon)
    compare_weapon()    (identify armed weapon by script name)
    count_item()        (count specific weapon items in the pack)
    count_magic()       (count specific magic items in the pack)
    free_items()        (count remaining free item slots in pack)
    free_magic()        (count remaining free magic slots in pack)
    kill_cur_item()     (delete currently armed weapon item)
    kill_cur_magic()    (delete currently armed magic item)
    kill_this_item()    (delete named weapon item from pack)
    kill_this_magic()   (delete named magic item from pack)


Mouse

  [The dink engine normally does very little with the mouse but does have
  commands to try if you want to use the mouse during gameplay.]

  Commands in the "Mouse" category:
    set_keep_mouse()  (keep mouse active during gameplay)
    set_mode()        (set mouse or normal game-play mode)
    sp_brain()        (brain 13 is mouse brain)


Player Input

  [These refer to the choice() and wait_for_button() mechanisms, the only
  ways in DinkC to interact directly with the player.]

  Commands in the "Player Input" category:
    choice()                (display menu and/or information to player)
    stop_entire_game()      (pause game while choice() menu is displayed)
    stop_wait_for_button()  (cancel another script's wait_for_button())
    wait_for_button()       (set &result based on key pressed by user)


Screen Drawing

  [Ordinarily, the screen is drawn automatically by the dink engine when the
  player moves Dink onto the screen.  This category of commands, often used in
  "warp" procedures (see "Appendix B: warp-eg.c") or in "cut scenes", are used
  to alter the appearance of the screen or control when it is drawn.]

  Commands in the "Screen Drawing" category:
    copy_bmp_to_screen()  (replace screen background with bitmap)
    draw_background()     (refresh screen background without running scripts)
    draw_hard_map()       (recalculate hardness after modifying sprites)
    draw_hard_sprite()    (recalculate hardness after modifying one sprite)
    draw_screen()         (draw last screen loaded)
    draw_status()         (draw or re-draw status area and sidebars)
    fade_down()           (fade-to-black effect)
    fade_up()             (undo fade_down())
    fill_screen()         (fill background & status areas with a single color)
    load_screen()         (prepare to draw a screen but don't draw it)
    show_bmp()            (display map or other full-screen bitmap)
    sp_noclip()           (prevent clipping of sprites in status area)
    sp_nodraw()           (make sprite invisible)


Script Management

  These commands allow scripts to invoke each other; attach themselves or
  other scripts to items or sprites; detect scripts in memory or attached
  to items or sprites; and remove scripts from memory.

  Commands in the "Script Management" category:
    compare_sprite_script() (check for a specific script attached)
    compare_weapon()        (identify armed weapon by script name)
    external()              (call a procedure in another script)
    is_script_attached()    (detect a script attached to a sprite)
    kill_this_task()        (remove current script from memory)
    run_script_by_number()  (run script detected by is_script_attached())
    script_attach()         (attach this script to another sprite)
    scripts_used()          (count scripts in memory)
    set_callback_random()   (repeatedly call another procedure in this script)
    sp_script()             (attach a named script to a sprite)
    spawn()                 (invoke another script as a concurrent task)


Sound

  [It's my considered opinion that computers should be seen and not heard ;).
  Okay, maybe I'm kidding, but the sound often attracts unwanted attention
  when I compute; and even when I'm alone, I quickly find many game sounds
  annoying, distracting, and repetitive.
     The point is that my speakers are turned off most of the time, so I
  don't know much about this category of commands yet.  They'll probably be
  the last ones I become comfortable with.]

  Commands in the "Sound" category:
    kill_all_sounds()    (cancel all sound_set_survive() commands)
    load_sound()         (load sound into memory and give it a number)
    playmidi()           (play .mid file or CD track)
    playsound()          (play sound loaded by load_sound())
    sound_set_kill()     (cancel a sound_set_survive() command)
    sound_set_survive()  (make a sound repeat and survive a screen change)
    sound_set_vol()      (reduce a sound's volume)
    sp_sound()           (attach a a sound with a sprite)
    stopcd()             (stop playing a CD track)
    stopmidi()           (stop a playmidi() playback)
    turn_midi_off()      (turn off midi attached to screen)
    turn_midi_on()       (undo turn_midi_off())


Sprite

  [In general, these are just commands that take or return a sprite number
  but do not fall into the Sprite Attribute category.]

  Commands in the "Sprite" category:
    busy()                  (detect whether sprite is "talking")
    compare_sprite_script() (check for a specific script attached)
    create_sprite()         (dynamically create a sprite)
    disable_all_sprites()   [mass sp_disable() command]
    enable_all_sprites()    [undo disable_all_sprites()]
    get_rand_sprite_with_this_brain()  (pick sprite at random)
    get_sprite_with_this_brain()       (detect specific type of sprite)
    is_script_attached()    (detect a script attached to a sprite)
    hurt()                  (hurt a sprite outside of combat situations)
    script_attach()         (attach this script to another sprite)
    say()                   (associate text with a sprite)
    say_stop()              (associate text with a sprite, pause script)
    say_stop_npc()          (non-interfering text display)
    sp()                    (get DinkEdit sprite # for dink engine sprite #)
    sp_editor_num()         (get dink engine sprite # for DinkEdit sprite #)


Sprite Attribute

  [These commands allow a script to do to a sprite what could also be done
  in DinkEdit, or to detect and/or change a DinkEdit-applied attribute.
  In Seth's original document, most of these appeared under the heading,
  "*** COMMANDS TO GET/CHANGE SPRITE ATTRIBUTES ***", which is now
  "Part 2A: 'Commands To Get/Change Sprite Attributes' notes".

  Not all DinkEdit attributes are addressable by script commands -- notably
  absent are warp properties, for example -- and as can be clearly seen, a
  lot of attributes not addressable in DinkEdit.]

  Commands in the "Sprite Attribute" category:
    sp_base_death()   (DinkEdit: Shift 6)
    sp_active()       (DinkEdit: none [implied by creation of sprite])
    sp_base_attack()  (DinkEdit: Alt 2)
    sp_base_idle()    (DinkEdit: unshifted 7)
    sp_base_walk()    (DinkEdit: unshifted 6)
    sp_brain()        (DinkEdit: unshifted 3)
    sp_defense()      (DinkEdit: Alt 3)
    sp_dir()          (DinkEdit: none)
    sp_disabled()     (DinkEdit: none [implied by creation of sprite])
    sp_distance()     (DinkEdit: none)
    sp_exp()          (DinkEdit: none)
    sp_flying()       (DinkEdit: none)
    sp_follow()       (DinkEdit: none)
    sp_frame()        (DinkEdit: none)
    sp_frame_delay()  (DinkEdit: none)
    sp_gold()         (DinkEdit: none)
    sp_hitpoints()    (DinkEdit: Shift 8)
    sp_hard()         (DinkEdit: unshifted 9)
    sp_kill()         (DinkEdit: none)
    sp_move_nohard()  (DinkEdit: none)
    sp_mx()/sp_my()   (DinkEdit: none)
    sp_noclip()       (DinkEdit: none [related to Z or X plus arrows])
    sp_nodraw()       (DinkEdit: none [similar to unshifted 2])
    sp_nohit()        (DinkEdit: Shift 9)
    sp_pframe()       (DinkEdit: Tab then E, etc. [sprite edit mode])
    sp_pseq()         (DinkEdit:  "   "   "   "      "     "    "   )
    sp_que()          (DinkEdit: unshifted 8)
    sp_range()        (DinkEdit: none)
    sp_script()       (DinkEdit: Shift 5)
    sp_seq()          (DinkEdit: Shift 4)
    sp_size()         (DinkEdit: unshifted 1)
    sp_sound()        (DinkEdit: Shift 7)
    sp_speed()        (DinkEdit: unshifted 4)
    sp_strength()     (DinkEdit: none)
    sp_timing()       (DinkEdit: unshifted 5)
    sp_touch_damage() (DinkEdit: Alt 1)
    sp_x() / sp_y()   (Dinkedit: set by sprite's placement)


Text Sprite

  [It is not an immediately intuitive concept, but a very important one to
  grasp, that text in Dink is considered a sprite.  These "text sprites" are
  created by one of the say() commands and, left to their own devices, "die"
  automatically in a few seconds.]

  Commands in the "Text Sprite" category:
    busy()                (detect whether a sprite is "talking")
    initfont()            (set font for text)
    say()                 (associate text with a sprite)
    say_stop()            (associate text with a sprite, pause script)
    say_stop_npc()        (non-interfering text display)
    say_stop_xy()         (display text anywhere on screen, pause script)
    say_xy()              (display text anywhere on screen)
    sp_brain()            (text sprites are brain 8)
    sp_kill()             (control how long a text sprite is displayed)


Undocumented

  [For whatever reasons, these were not documented in the original DinkC.txt.]

  Commands in the "Undocumented" category:
    arm_magic()             (arm the spell in pack slot &cur_magic)
    arm_weapon()            (arm the weapon in pack slot &cur_weapon)
    compare_sprite_script() (check for a specific script attached)
    get_version()           (get dink engine version)
    load_game()             (restore a saved game)
    game_exist()            (check to see whether save file SAVE#.DAT exists)
    get_burn()              (unknown)
    load_sound()            (load sound into memory and give it a number)
    set_button()            (change definition of game keys)
    sp_base_death()         (base value for death graphic)
    sp_base_idle()          (base value for standing-still seq's)
    sp_follow()             (make sprite follow another at a short distance)
    sp_gold()               (set a sprite's undocumented "gold" attribute)
    sp_nohit()              (test/apply DinkEdit "nohit" [Shift 9] property)
    sp_notouch()            (unknown)
    sp_picfreeze()          (unknown)


Variable Definition

  [The only type of variables that may be defined in DinkC are 32-bit integers
  (range of values +2147483647 to -2147483648).

  Variables may be either "global" or "local".  Global variables may be
  referenced or changed in any script known to the dmod; "local" variables
  are known only within a single script (though usually to all procedures in
  the script -- see command "int" for more details.)  Also note that the
  current values of all global variables are included in save files -- see
  save_game().
  
  "Global" and "local" are terms that define a variable's "scope", the context
  in which a variable is known.  In real C/C++, several more levels of "scope"
  are recognized.

  Variable Naming Rules:
  (1) Variable names MUST begin with an ampersand (&).  The & is considered
      part of the variable name, it is NOT an "address of" operator as in
      standard C.
  (2) The & must be followed by 1 to 18 characters to complete the variable's
      name.  By convention, C rules are followed (first character a letter or
      underscore, remainder letters, underscores, or numbers); but in point of
      fact, DinkC enforces no such rules.  Virtually any non-blank characters
      may be used to complete the variable name, including arithmetic
      operators such as + and -.

  There are two important and closely related restrictions on variable names,
  however: restrictions that are peculiar to DinkC.

  First, a script cannot define a local variable with the same name as a
  global variable.  Any attempt to do so results in the local definition being
  ignored and the global variable being used instead.  While this might seem
  logical, this restriction does not apply to standard C/C++.

  Second, even more peculiar to DinkC, is that no local variable name may be
  a global variable's name with a suffix.  For example, if a script tries to
  define a local variable &magic_lamp, no local variable is created. Instead,
  all references to &magic_lamp are treated as references to the global
  variable &magic!
     After much analysis on this restriction and the many exceptions I have
  seen that work fine, I THINK this is the rule: the dink engine searches a
  table of global variable names first, and correctly distinguishes names
  there.  Thus:
   - The dink engine is able to correctly distinguish the globals &magic and
     &magic_level, &life and &lifemax, and any other such names as long as
     they are all globals.
   - If the search of the global name table comes up empty, then the engine
     is able to correctly distinguish local variables that are named this way.
     For example, script s1-h1-4.c, as released by RTSoft, defines a local
     variable &who as well as &who2, &who3, and more, with apparently no
     problem with ambiguity since there is no global named &w, &wh, or &who.
     However...
   - A local variable named &gold_key, for example, will always be ignored,
     and its references treated as references to &gold, because &gold is a
     global and that table is always searched first.

  ManaUser reports that even the above cannot be trusted. His note, for your
  consideration: "I have as certain magic spell that gives &magic_level a
  special sentinel value (which it can do because this spell costs nothing).
  Ironically, I did this in an attempt to work around the lack of a working
  compare_magic() command.  Anyway, I found out that &magic_level and
  &magic_cost don't work in in "if" statement or say() command.  They do
  work in assignments statements, of course, so I worked around it with a
  temporary local variable...  I'd be inclined to guess assignment statements
  are all they work in."

  So, to be safe when you have control over variable names, always create
  names that cannot be confused with a shorter name.

  Maximum number of variables:
  With some help and prodding from Sylvain Beucler, I have determined that
  the dead maximum number of variables that the dink engine will support is
  248.  This number is the limit on the total of:
   - all make_global_int() commands, plus
   - all "int" commands in all active scripts -- see int, kill_this_task(),
     and scripts_used() for more comments on this. ]

  Commands in the "Variable Definition" category:
    int                 (define local variable)
    make_global_int()   (define global variable)


Weapon

  Commands in the "Weapon" category:
    activate_bow()        (start timed counter for firing bow)
    add_item()            (add a melee weapon to Dink's inventory)
    add_magic()           (add a magic attack to Dink's inventory)
    arm_magic()           (arm the spell in pack slot &cur_magic)
    arm_weapon()          (arm the weapon in pack slot &cur_weapon)
    compare_weapon()      (identify armed weapon by script name)
    get_last_bow_power()  (return activate_bow()'s counter)
    init()                (load sequences of Dink with or without his sword)
    kill_cur_item()       (delete currently armed melee weapon)
    kill_cur_magic()      (delete currently armed magic attack)
    kill_shadow()         (kill a sprite's "shadow" sprite)
    kill_this_item()      (delete a melee weapon)
    kill_this_magic()     (delete a magic attack)
    sp_attack_hit_sound() (sound effect when weapon hits something)
    sp_attack_hit_sound_speed()  [pertains to above]
    sp_brain()            (brains 11 & 17 are missile brains)
    sp_distance()         (sets the range of melee weapons)
    sp_nocontrol()        (freeze sprite until an animation is played)


(none defined)

  [No defined category for these yet.]

  Commands in this category:
    reset_timer()  (set game timer shown in save/load menus)
    debug()        (write text to debug.txt in debug mode)
    inside_box()   (detect sprite's general location)
    kill_game()    (exit to Windows)
    random()       (generate pseudo-random number)


    **********************************************************************
   ******************** Alphabetical List of Commands *********************
    **********************************************************************


#activate_bow()
# Category: Weapon
# Prototype:
#   void activate_bow( void );

  Special hack needed for the bow weapons' "charge up" time stuff.  (Check
  item-b1.c for example)

  If you use this, keep in mind the bow animations base is 100 and is 6 frames
  per dir[ection] (it uses 8 directions).  This is hardcoded.

  [Extract of code from item-b1.c:
     void use( void )
     {
        activate_bow();
        &mypower = get_last_bow_power();
        <create missile/arrow with strength based on &mypower and &strength>
     }
  If the Ctrl key is already being pressed when activate_bow() is issued (as
  must be the case in a weapon's use() procedure), the script is paused until
  the Ctrl key is released.  During this time, a counter is quickly incremented
  based on how long the Ctrl key is held.  In about one second, the counter
  reaches a ceiling value of 500 -- the script remains paused but the counter
  is not incremented further.  get_last_bow_power() then returns the value of
  the counter when the Ctrl key is released and the script is allowed to
  continue.
     If the Ctrl key is not being held, activate_bow() does not pause the
  script and get_last_bow_power() returns a single-digit number, usually zero.]

  See also: get_last_bow_power()


#add_exp()
# Category: Dink Specific
# Prototype:
#   void add_exp( int amount, int sprite_to_get_x_y_cords_from );

  Lets you add experience visually correct, so the # floats above something.

  [Seems to work in some situations and not others.  ManaUser: "It seems to
  only work in a script attached to a sprite that was last hit by Dink."
  When it does not work, all you can do is add experience to &exp directly.

  It made the most sense when I used this in a die() procedure that changed
  the monster's brain to 12 when Dink finished it off.  Brain 12 causes the
  sprite to quickly grow or shrink to a specified size and then die when it
  reaches that size.  But this resulted in Dink not getting any experience.
  The solution went something like this:

     void die( void )
     {
       freeze(&current_sprite);
       // give Dink his experience:
       int &dinks_exp = sp_exp(&current_sprite, -1);
       add_exp(&dinks_exp, &current_sprite);
       // if sprite was placed by the editor, make it not come back:
       int &hold = sp_editor_num(&current_sprite);
       if (&hold != 0) editor_type(&hold, 1);
       //shrink to this percent then die:
       sp_brain_parm(&current_sprite, 10);
       sp_brain(&current_sprite, 12);
     }

  (end of add_exp() notes) ]

  See also: sp_exp()


#add_item()
#add_magic()
# Category: Inventory, Weapon
# Prototype:
#   int add_item( char scriptname[8], int seq, int frame );
#   int add_magic( char scriptname[8], int seq, int frame );

  [Add_item() adds an item to the 16-slot weapons area of Dink's inventory.
  Add_magic() adds an item to the 8-slot magic area.  The <scriptname>
  argument, if it names a real script, causes that script to be loaded and
  executed as described in "Part 4: How Items Work."  Whether or not it names
  a real script, the <scriptname> argument still serves to identify the item
  to the other commands in this category, such as count_item() and
  kill_this_item().

  The <seq> and <frame> arguments specify a graphic to represent this item
  in the inventory and status area.  In the original Dink Smallwood graphics,
  sequence 437 is magic inventory symbols and 438 is weapon inventory symbols.

  Despite the fact that the prototype indicates these commands provide a
  return value, I have found no circumstance under which they return anything
  other than 0.]

  See also:
    free_items(), free_magic() to see if there is room in the pack for an item;
    count_item(), count_magic() to count how many of an item Dink is carrying;
    kill_this_item(), kill_this_magic() to remove an added item again;
    arm_weapon(), arm_magic() to arm an added item;
    compare_weapon() [but not compare_magic()] to tell what item is armed;
    kill_cur_item(), kill_cur_magic() to kill the currently-armed item;
    "Part 4: How Items Work" for general information; and
    kill_this_task() for more notes on writing item scripts.


#arm_magic()
# Categories: Inventory; Weapon; Undocumented
# Prototype:
#   void arm_magic( void );

  [Magic-item counterpart of arm_weapon(), below.  Arms the magic in the
  magic-inventory slot (1-8) specified in &cur_magic, or disarms magic if
  &cur_magic is 0.  This command does not update the status area: you will
  usually want to draw_status() after using this command.  Example:

      // disarm Dink's magic:
      &cur_magic = 0;
      arm_magic();
      draw_status();

  There is no direct way to know whether a given slot is empty or what is in
  it -- command compare_magic() is non-functional.]

  See also: arm_weapon(), draw_status(); variable &cur_magic


#arm_weapon()
# Categories: Inventory; Weapon; Undocumented
# Prototype:
#   void arm_weapon( void );

  [Arms the weapon in the inventory slot (1-16) specified by &cur_weapon.
  Typical usage in many dmods' start-1.c:

    add_item("item-fst", 438, 1);
    &cur_weapon = 1;
    arm_weapon();

  In this context it does pretty good.  For more complex situations, it has
  a serious shortcoming: &cur_weapon must be set to a slot number from 1-16,
  designating a weapon slot in Dink's pack.  What's in a given slot? What
  slot contains an item detected by count_item()?  Once the game is underway,
  there's just no way to know; though this sample code partially overcomes
  this problem:

    // arm claw sword if Dink is carrying it:
    int &claw = count_item("item-sw2");
    if (&claw > 0)
    {
      &cur_weapon = 0;
    loop:
      &cur_weapon += 1;
      arm_weapon();
      // note: loop will not abort if this happens to "arm" an empty slot
      &claw = compare_weapon("item-sw2");
      if (&claw != 1)
         goto loop;
      // claw sword armed -- show it in status area:
      draw_status();
    }

  As shown in the above example, draw_status() must be used to show the
  newly-armed weapon in the status area.]

  See also: arm_magic()


#busy()
# Categories: Sprite, Text Sprite
# Prototype:
#   int busy( int sprite );

  Returns 0 if this sprite is not 'talking' to someone.  Else it returns the
  sprite # of the speech [that is, of the text sprite].

  [ManaUser provides a example use for this function, apparently inspired by
  Seth's example use for script_attach(1000): "I had a script that kept running
  all the time (attached to sprite 1000) to make Dink comment on the fact that
  he was poisoned from time to time.  If he was talking to someone when this
  happened the game would lock up.  So I made sure busy(1) was 0 before making
  him say anything."  The heart of such code might have looked like this:

     void main( void )
     {
        int &isbusy;
     loop:
        if (&poisoned == 0)
            kill_this_task();
        &isbusy = busy(1);
        if (&isbusy == 0)
            say("I'm poisoned...", 1);
        wait(5000);  // Seth's example delay, probably too short
        goto loop;
     }

  ManaUser advises that the output of busy() has to be assigned to a variable
  and tested as shown above, that "if (busy(1) == 0)" does not work.]

  See also: say(), say_stop()


#choice()
# Category: Player Input
# Prototype:
#   choice_start()
#     set_y 240
#     set_title_color 15
#
#     title_start();
#   Title line 1
#   optional additional title line(s)
#     title_end();
#
#     (optional "if" type conditional(s)) "choice 1"
#     "optional additional choice(s)"
#   choice_end();

  [Notes:
   (1) This is only intended as a quick overview of the choice() syntax -- see
       "Part 5: How the Choice Statement Works" for the complete description.
   (2) It is unclear whether Seth intended for the "choice_start()" and/or
       "choice_end()" lines to be followed by a semicolon(;), but it doesn't
       matter: their presence or absence has no effect on how the construct
       operates.
   (3) The semicolons are, however, very definitely required on the
       "title_start();" and "title_end();" lines.
   (4) The values shown on the "set_y" and "set_title_color" lines are merely
       examples.  "240" is a typical value for "set_y"; "15" is the maximum
       value for set_title_color.]

  See also: wait_for_button(); stop_entire_game();
            "Part 5: How the Choice Statement Works"


#compare_magic()

  [This command is supposed to be the counterpart of compare_weapon(), but
  it does nothing useful.  See "Part 4: How Items Work".]


#compare_sprite_script()
# Categories: Script Management; Sprite; Undocumented
# Prototype:
#   int compare_sprite_script( int comp_sprite, char comp_script[8] );

  ManaUser: "Returns 1 if the script attached to the <comp_sprite> is named
  <comp_script>; otherwise, returns 0."

  [This command was used several times in the original game to tell, in a hit()
  procedure, whether a sprite was hit by a bomb.  Sample code from s4-rock.c:

     void hit( void )
     {
        int &rcrap = compare_sprite_script(&missle_source, "dam-bomn");
        if (&rcrap == 1)
        {
        //rock just got hit by a sprite with a script named dam-bomn, I'm
        //gonna guess it was the bomb.
        ...

  See s4-rock.c, as well as s4-sec1.c and s5-sec1.c, as released by RTSoft.
  Note that not all weapons update &missle_source, so Seth's use of the word
  "guess" is appropriate.]

  See also: compare_weapon(); is_script_attached()
    [there is also a compare_magic() command, but it is non-functional]


#compare_weapon()
# Categories: Inventory; Script Management; Weapon
# Prototype:
#   int compare_weapon( char name_of_item_script );

  Example: compare_weapon("ITEM-B1") would return 1 if the armed item's script
  was item-b1 [or 0 otherwise].  Used in s3-gobg.c [where Dink is allowed into
  the goblin village only if he has a bow *armed*.]

  [Note: this function can also be used in a hit() procedure, for example, to
  infer what the attached sprite was hit with.  It is only a guess, however --
  it checks Dink's armed melee weapon, while hit() could have been activated by
  a magic attack.]

  See also: compare_sprite_script()
            [but not compare_magic(), which is non-functional]


#copy_bmp_to_screen()
# Category: Screen Drawing, Cut Scene
# Prototype:
#   void copy_bmp_to_screen( char string[200] );

  Copies any [see notes] .bmp file to the screen. Good way to not waste memory,
  needs to be Dink's palette.  (or whatever splash.bmp is, this is where Dink
  gets its base palette [note 5])

  [Notes:
    1) As Seth says, the bitmap must have the "dink palette" (see also note 4);
       and as with show_bmp(), the size must be 640x480.
    2) The displayed bitmap covers the entire screen, including the status and
       screenlock areas; but it does not cover any sprites on the screen and
       the game is not paused.  Dink can continue to move around and interact
       with the other sprites on the screen just as though this command had
       not been used.  Effectively, it only replaces the screen's background
       tiles.
    3) draw_status() must be used to restore the status and screenlock areas
       after this command is used (assuming, that is, that you want the status
       displayed normally again.)
    4) ManaUser: "A couple of interesting things on copy_bmp_to_screen(). The
       image must be 640x480 or it messes up the screen and will not display.
       Also, if the image has a different palette, the game will load that
       palette and keep using it.  Something to look out for, but it could
       have some interesting uses, too..."
    5) Gary Hertel included this comment in an email on another topic: "And
       despite what everyone says, the dink palette is not set by the splash
       screen but rather by one of the tiles or files in the tile directory."
          I don't yet know how to confirm or refute Gary's statement: I am
       simply passing it along for your consideration. ]

  See also: draw_background(), draw_screen(), draw_status() [for ways to fix
    the areas changed by this command]; fill_screen() [for a way to alter the
    screen background without a bitmap]; show_bmp() [for another approach to
    displaying bitmaps]


#count_item()
#count_magic()
# Category: Inventory
# Prototype:
#   int count_item( char name_of_item_script );
#   int count_magic( char name_of_item_script );

  [Counts the number of items with the specified script name currently in the
  inventory.  Count_item() counts only items added by add_item(); count_magic()
  counts only add_magic() items.  Interestingly, the specified script need not
  actually exist: it only has to be the same as the corresponding add_item()
  or add_magic() command's script-name argument.]

  See also:
    add_item(), add_magic() to add items to the pack;
    free_items(), free_magic() to count free slots in the pack;
    kill_this_item(), kill_this_magic() to remove items;
    "Part 4: How Items Work" for general information.


#create_sprite()
# Categories: Cut Scene; Sprite
# Prototype:
#   int create_sprite( int x, int y, int brain, int pseq, int pframe );

  Creates a new sprite.  Returns the actual sprite #.  (Uses the 1st unused
  slot it can find, returns 0 if all 300 are already in use.)

  [One use of create_sprite() is to create the rewards for killing a monster.
  Here's a couple of examples, see make.c for more...
    giant red full-heal heart:
      int &redheart = create_sprite(&save_x, &save_y, 6, 54, 1);
      sp_script(&redheart, "heart");
    random(150,50) amount of gold:
      int &gold200 = create_sprite(&save_x, &save_y, 6, 178, 4);
      sp_script(&gold200, "gold200");

  Other create_sprite() examples...
    animated red star often used as a warp sprite:
      int &bad = create_sprite(320, 200, 6, 170, 1);
      sp_script(&bad, "warp-eg");  // see appendix B for "warp-eg"
    magic potion:
      int &ppot = create_sprite(320, 200, 0, 0, 0)
      sp_script(&ppot, "ppotion");

  Note that it is common to simply use 0's for the last three arguments. Most
  standard scripts contain all the necessary sp_xxx() commands to insure that
  they don't care what kind of a sprite they are attached to, they will turn
  it into the kind of sprite they are meant to support anyway.  Attach
  "ppotion" to a tree, a bonca, or whatever, and the player will only see a
  jumping purple bottle that disappears and increments Dink's magic stat when
  he touches it.
     Follow-up: I do not recommend using 0's for the last three arguments.
  There is a bug in the dink engine that can cause a newly-created sprite's
  touch() procedure to be run as soon as it is created, whether Dink is
  touching it or not.  I have had good luck controlling this bug by supplying
  valid arguments, <brain> in particular, in the create_sprite() command.

  Tyrsis: "If your script generates a sprite, but you don't see it (it is
  there, it works but doesn't show), it means the sequence is pushed out of
  memory cache. The debugger gives 'bad pix in sequence' message.  Just use
  preload sequence."  Thanks, Tyrsis -- I encountered this exact problem
  while debugging a dmod.  See preload_seq() for more details.]

  See also:
    preload_seq()  [to fix problem with invisible sprites]
    sp_active()    [to delete sprite again]
    Part 2B, "About PSEQ and PFRAME"


#debug()
# Category: (none defined)
# Prototype:
#   void debug( char string[200] );

  Writes to the debug.txt file if the -debug parm is set when dink.exe is run
  [or if Alt+D is pressed during gameplay].  Variable names can be used inside
  the string.

  [I have used this feature of the dink engine several times and have yet to
  find it useful.  Temporary say() commands provide immediate feedback during
  the debugging process.]


#dink_can_walk_off_screen()
# Categories: Cut Scene; Dink Specific
# Prototype:
#   void dink_can_walk_off_screen( bool 1_or_0 );

  Pass 1 and Dink can walk off the screen and not trigger a 'screen scroll' to
  the next screen. [Said another way, this command makes the game's camera stay
  on the current screen instead of following Dink.  This is strictly intended
  as a cut-scene command: use "dink_can_walk_off_screen(0)" when the scene is
  done to return to normal game play.  Otherwise, the player will find that
  Dink can move off the edge of this screen but can't get to the next one.]


#disable_all_sprites()
#enable_all_sprites()
# Categories: Sprite; Undocumented
# Prototype:
#   void disable_all_sprites( void );
#   void enable_all_sprites( void );

  [These undocumented commands were discovered by ReDink1: "these commands
  seem to work!  Probably not of any use, but still cool.  Using a test of
  a script attached to 0, I disabled all sprites, waited a second, then
  enabled them.  All sprites disappear (except the status bar, et al) and
  then reappeared.  I'm not sure what happens to the scripts/movement/keys/
  Dink while they are disabled, though..."  Might be worthwhile to find out,
  this could be another command useful for creating a special effect.

  See also: sp_disable()


#draw_background()
# Category: Screen Drawing
# Prototype:
#   void draw_background( void );

  Draws the background (tiles and all type 0 sprites).  After seriously playing
  with the screen (like doing a fill_screen) this can fix it up.  All dead
  bodies, etc., will be missing, so keep this in mind.

  [This command can be used in place of draw_screen() if no sprites are messed
  up by whatever special effect causes the need to refresh the background.
  Choose between draw_screen() and draw_background() based on whether scripts
  should be re-run: draw_screen() runs the main() procedures of the map's
  "base" script and all the sprites' attached scripts; while this command,
  draw_background(), causes no attached scripts to be re-run.]

  See also: draw_screen(), fill_screen()


#draw_hard_map()
# Categories: Cut Scene; Screen Drawing
# Prototype:
#   void draw_hard_map( void );

  This will recalculate ALL hardness based on what is currently on the screen.
  If you dynamically turn off a sprite, it will leave the screen, but if the
  sprite has type 0 hardness it will still be there until this is called. This
  isn't very fast, so don't use it on a regular basis, but instead for special
  things.

  [Notes:
    1) This command is commonly used to redraw hardness in two situations:
       after a hard sprite is moved, such as when a rock is pushed, and when
       a sprite is made to disappear.  Examples from code released by RTSoft...

       Extract of code from s1-h1-4.c -- make several sprites disappear:

          void main(void)
          {
            if(&story > 3)
            {
              int &who1 = sp(22);
              int &who2 = sp(23);
              sp_active(&who1,0);
              sp_active(&who2,0);
              draw_hard_map();
            }
          }

       Extract of code from S1-ROC.C -- push a rock:

          void push( void )
          {
             int &mydir = sp_dir(1, -1);
             if (&mydir == 6)
             {
               say("It's .. it's moving...", 1);
               freeze(1);
               move_stop(&current_sprite, 6, 350, 1);
               unfreeze(1);
               draw_hard_map();
             }
          }

    2) It is unclear what Seth means by, "This isn't very fast..."  It seems
       fast enough to me, at least when it is used just once, as in the above
       examples.  I assume he recommends against using it in a loop, however.]

  See also:
    draw_hard_sprite()  [alternate hardness recalculator]
    move(), move_stop(), sp_active(), sp_hard()
                        [commands that often require hardness recalculation]


#draw_hard_sprite()
# Categories: Cut Scene; Screen Drawing
# Prototype:
#   void draw_hard_sprite( int sprite_number );

  Same as above, but limited to ONE sprite's hardbox.  MUCH faster than the
  above.  Breaking barrels use this.

  [Because of Seth's statements that draw_hard_map() "isn't very fast" and
  draw_hard_sprite() is "MUCH faster", I have seen scripts that seem to avoid
  draw_hard_map() religiously, preferring to do a number of draw_hard_sprite's
  instead.  This seems an unnecessary precaution.  In a script like s1-h1-4.c,
  where a number of sprites are made to disappear, doing one draw_hard_map()
  after a list of sp_active(<sprite>,0)'s only makes sense.

  On the other hand, it seems like it would make sense to take advantage of
  draw_hard_sprite()'s alleged speed in S1-ROC.C, where only a single sprite
  is involved, but it doesn't work.  draw_hard_sprite() adds the hardness for
  the sprite's new location, but does not remove the hardness from its old one.

  In any case, a single execution of draw_hard_map() takes no perceivable
  amount of time on most screens, while the delay from a number of draw_hard_
  sprite() commands can become noticeable.  Use whichever one makes sense in
  the current context.]

  See also:
    draw_hard_map()        [alternate hardness recalculator]
    sp_active(), sp_hard() [commands that often require hardness recalculation]


#draw_screen()
# Category: Cut Scene, Screen Drawing
# Prototype:
#   void draw_screen( void );

  Draws the screen.

  [Notes:
    1) This command performs two basic actions:
       - draws the last screen loaded, either explicitly by load_screen() or
         implicitly when Dink walked off the edge of some other screen and
         onto this one; and
       - runs the main() procedures of any scripts attached to the screen or
         its sprites.
       The main() procedure of any script attached to the screen is run before
       the screen is drawn; the main()'s of any scripts attached to sprites
       are run afterwards.
    2) This command is invariably used in one of two situations: (1) to
       re-draw the current screen after it has been messed up by certain
       commands such as fill_screen() or copy_bmp_to_screen(); or, more often,
       (2) after a load_screen().
    3) If the script doing the draw_screen() is attached to a screen or a
       sprite, that script is abandoned when the draw_screen() command is
       started.  Use script_attach(1000) to prevent this from happening.
       Alternatively, just make sure that main() in the new screen's script
       does any cleanup after the warp, such as a fade_up() or sp_nodraw(1,0).
    4) load_screen() and draw_screen() cannot be used to change "vision".
       If the screen has an attached ("base") script, it will be invoked to
       handle vision number the same as always; and if not, &vision is reset
       to zero. ]

  See also:
    draw_background()        [for an alternative to draw_screen()]
    force_vision()           [redraw screen and run scripts]
    load_screen()            [prerequisite command]
    draw_status()            [recommended by Seth after draw_screen()]
    "Appendix B: warp-eg.c"  [example warp procedure]
    script_attach()          [mentioned in notes above]


#draw_status()
# Category: Cut Scene, Screen Drawing
# Prototype:
#   void draw_status( void );

  Draws the status bar and side bars.  [Good thing to use after using the semi-
  undocumented commands arm_weapon() or arm_magic().]

  To warp someone in a script, change &player_map and then call [these three
  procedures: load_screen(), draw_screen(), and draw_status().]

  [Despite Seth's instruction to use draw_status() in a warp procedure, that is
  usually unnecessary.  Unless the status area has been messed up (such as by
  fill_screen()), or the status is changed by a routine that doesn't update the
  status area automatically (such as arm_weapon()), there is no need to use
  draw_status().  Fade_down(), for example, makes the status area invisible but
  does not change it in any way that is not reversed by fade_up().  The status
  area contains no screen-dependent information, so it does not normally need
  to be redrawn when moving to a new screen.
     Probably the only exception would be if sp_noclip() applied to a sprite
  on either the old screen or the new one.]

  See also:
    "appendix warp_eg.c"          [for a complete, annotated warp procedure]
    load_screen(), draw_screen()  [other warp-procedure commands]
    arm_weapon(), arm_magic(), fill_screen(), sp_noclip()
                                  [referenced in above notes]


#editor_seq()
#editor_frame()
# Category: Editor Data
# Prototype:
#   int editor_seq( int DinkEdit#, int value <-1 to not change> );
#   int editor_frame( int DinkEdit#, int value <-1 to not change> );

  [The <DinkEdit#> argument is the sprite's number as assigned by DinkEdit:
  use sp_editor_num() to retrieve it.  The <value> argument is included in
  save files.  When editor_type() (below) is 0, the <value> stored by
  editor_seq()/editor_frame() has no particular meaning to the dink engine
  and, within limits, may be used to store values unrelated to the commands'
  intended purpose.  The value is 0 by default and may be retrieved by
  passing -1 as the <value> argument.]

  See editor_type() (below) and "Part 3: A Changing World" for a complete
  description of this facility; see also sp_editor_num().


#editor_type()
# Category: Editor Data
# Prototype:
#   int editor_type( int DinkEdit#, int value <-1 to not change> );

  [The <DinkEdit#> argument is the sprite's number as assigned by DinkEdit:
  use sp_editor_num() to retrieve it.  The <value> argument is included in
  save files.  When 0, the default(*), the sprite is loaded as it was defined
  in map.dat by DinkEdit.  Valid non-zero values are as follows:]

  1 - kill sprite completely
  2 - draw pic from enclosed seq/frame data as a sprite WITHOUT hardness
  3 - draw pic from enclosed seq/frame data as a BACKGROUND object WITHOUT
      hardness (can be walked on [but not] behind)
  4 - draw pic from enclosed seq/frame data as a sprite WITH hardness
  5 - draw pic from enclosed seq/frame data as a BACKGROUND object WITH
      hardness (can't walk [on or] behind)
  6 - kill sprite, but let him come back after 5 minutes
  7 - kill sprite, but let him come back after 3 minutes
  8 - kill sprite, but let him come back after 1 minute

  [ManaUser noted that the descriptions of editor types 3 and 5 were wrong
  in the original document; the correct descriptions are as shown above.

  More notes:
  (*) I said above that type 0 is the default; and as far as I know, that is
      always supposed to be the case.  Certainly I know of no way to specify a
      different value in DinkEdit.  But I have occasionally run into exceptions
      in the dmods I have tinkered with, where a sprite I expected to see was
      not drawn and analysis showed that its editor_type, as inherited from
      DinkEdit, was not zero.  I have had good luck fixing this glitch using
      either of these methods:
        - in DinkEdit, create a copy of the sprite ("pick it up" with Enter,
          "Stamp" it down, then Enter again) and then delete the original.
          The bogus editor_type is not part of the copied information; in
          point of fact, it is probably set by the dink engine when a saved
          game is loaded rather than when the DinkEdit files are read.
        - in the screen's "base script", set the editor_type for the sprite's
          editor number to 0.  This should be done before the screen is drawn
          (that is, before any wait() command in the base script); and if the
          sprite can legitimately be picked up or "killed", there must be
          logic that avoids resetting editor_type once this event has happened.

  (1) "enclosed seq/frame" means values supplied by the editor_seq() and
      editor_frame() commands, above, which are also stored in save files.

  (2) The concept of "with hardness" (types 4 and 5) versus "without hardness"
      (types 2 and 3) always made sense to me, but the essential difference
      between a "sprite" (types 2 and 4) and a "background object" (types 3 and
      5) took me a little longer.  But this is the very same concept as the
      normal versus ornamental ("2" key) attribute in DinkEdit. A normal/sprite
      object is assigned a sprite a number when the screen is drawn; an
      ornamental/background object is not.  Once a sprite is set to type 3 or
      5, Dink can no longer interact with it; any "brain" it had is inactive;
      any script associated with it is no longer loaded or run.

  (3) Remember that changes to a sprite's editor_type() are implemented the
      next time the screen is drawn, not immediately.  It is valid, though
      unusual, to use draw_screen() to force a change to be reflected
      immediately.]

  See also: editor_seq(); editor_frame(); sp_editor_num();
            "Part 3: A Changing World"


#enable_all_sprites()
  see disable_all_sprites()


#external()
# Category: Script Management
# Prototype:
#   void external( string name_of_c_file, string name_of_proc );
# Example:
#   external("make", "func1");

  This is how you run a procedure in another script.  It will return when it
  is finished.

  [Note that external() always runs a new instance of the named script, even
  if another instance is already present in memory.  If it is important to run
  an existing instance -- so that the called procedure will have access to the
  current values of the running script's local variables, for example -- then
  you need to seek a way to use run_script_by_number().

  Sylvain Beucler: "The argument is not the filename, but a relative path, and
  it is not limited to 12 or so characters."  This allows some scripts in a
  dmod's "story" folder to be moved to a subfolder.  Example:

     external("utility\make", "gheart");

  This would run function "gheart" in "...dmod\story\utility\make.c".]

  See also:
    "my_proc()"  [to invoke a function in this script]
    spawn()      [for another way to invoke an external script]


#fade_down()
# Category: Cut Scene, Screen Drawing
# Prototype:
#   void fade_down( void );

  The screen will fade to black.  It's up to you to call the following command.
  The script is paused until this is accomplished(*), then continued.

  [ManaUser offers a couple of notes:
   (1) It's probably better not to fade_down() without freezing Dink first.
       If the user starts a new game or something happens to Dink while the
       screen is dark, the game could get stuck dark unless you specifically
       anticipate and code for these situations.
   (2) Note that text, and anything pure white, stays visible in a
       fade_down().]

  See also: fade_up(), "appendix B: warp-eg"


#fade_up()
# Category: Cut Scene, Screen Drawing
# Prototype:
#   void fade_up( void );

  Yay, we can see again.  Script is paused until this is accomplished(*), then
  continued.

  [(*) In other words, fade_up() runs in "real" or human-perceivable time, like
  wait() and say_stop().  Meaning that it should not be used in a time-critical
  situation, like a die() procedure that has not yet done a script_attach(1000).

  This makes it fundamentally different from fade_down(), which allows the
  script to continue while the screen is fading down.  fade_down() is more like
  say() or move() whereas fade_up() is more like say_stop() or move_stop().]

  See also: fade_up(), "appendix B: warp-eg"


#fill_screen()
# Category: Cut Scene, Screen Drawing
# Prototype:
#   void fill_screen( int color );

  Fills the entire screen a certain color.  0 is black.  [...and 255 is white.
  ManaUser notes that all other colors, 1-254, follow the "dink palette," which
  can be viewed by loading "...\Dink Smallwood\dink\tiles\splash.bmp" into any
  good graphics program.  I suggest IrfanView ( www.irfanview.com ), where
  you can then select Image from the main menu, Palette from the Image menu,
  and Edit Palette from the Pallette menu.]

  [Here is a fun little routine:

     void main( void )
     {
        int &ctr = 50;
        int &col;
        int &wt;
     loop:
        if (&ctr)
        {
           &ctr -= 1;
           &col = random(256, 0);
           fill_screen(&col);
           &wt = random(100,10);
           wait(&wt);
           goto loop;
        }
        wait(50);
        draw_background();
        draw_status();
        kill_this_task();
     }

  Put this in a script called key-187.c, for example, press the "=" key, and
  watch the show.  Among other things, it demonstrates that fill_screen() fills
  the background, status, and border areas; but it does NOT erase any sprites
  on the screen, at least not visibly.  The possibilities are endless...]

  See also: draw_background(), draw_screen(), draw_status() [for ways to fix
    the areas changed by this command]; copy_bmp_to_screen() [for a way to
    change the fill_screen() areas with more finesse]


#force_vision()
# Category: Cut Scene
# Prototype:
#   void force_vision( int new_vision );

  Changes vision and updates screen to reflect vision changes - basically like
  walking onto a new screen, but doesn't change Dink's location.  Warning: it
  kills all scripts and sprites and reconstructs them using the new vision. It
  also makes the script being run stay alive by connecting it to sprite 1000
  (script never dies).  You must use kill_this_task() or it will NEVER die.
  ["Never," that is, until the next load_game() command.]

  [Notes:
   (1) All that is necessary to change vision is to change &vision in the
       main() procedure of a screen's base script.
   (2) After a great deal of debate and experimentation, I remain convinced
       that this command is pretty much the same as:
         script_attach(1000);
         draw_screen();
       In other words, this command simply causes the current screen to be
       re-drawn, a process that includes running the screen's base script (if
       it has one) and the main() procs of all scripts attached to sprites.
       As near as I can tell, the "new_vision" argument is ignored and may
       be omitted without affecting the command's operation.

  Examples are based on scripts for the main Dink Smallwood game as released
  by RTSoft

  Example 1: s1-h1-o.c, the base script for screen 439, Dink's house:
      void main( void )
      {
        if (&story == 4)
        // set by s1-h1-s when Dink sees his mother dead
        {
          // cut scene of neighbors gathered around, then...
          &story = 5;
          force_vision(2);
          kill_this_task();
        }
        if (&story > 4)
        // as set above...
        {
          &vision = 2;
          // THIS is how &vision gets set to 2, not by the force_vision(2);
        }
      }

  Example 2: s1-brg2, the hustler on the bridge, screen 404:
      void talk( void )
      {
        ...
        if (&result == 1)
        // Dink agrees to pay:
        {
          ...
          &story = 7;
          force_vision(0);
          kill_this_task();
        }
      }
    s1-brg is the screen's base script:
      void main( void )
      {
        if (&story > 6)
        // Dink has paid the man...
        {
          return;
          // leaving &vision set to 0, ALWAYS its default value
        }
        &vision = 1;
        ...
      }

  Conclusion: there is no way in Dink to change a screen's vision except for
  its base script to assign a value to &vision. ]

  See also: draw_screen()


#free_items()
#free_magic()
# Category: Inventory
# Prototype:
#   int free_items( void );
#   int free_magic( void );

  [Counts how many free slots remain in Dink's pack.  free_items() counts how
  many of the 16 weapon/item slots remain unused; free_magic() counts how many
  of the 8 magic slots still remain.  ManaUser: "this is mostly used just to
  check if Dink's inventory is full."]

  Example [from s1-nut.c as released by RTSoft]:

  void touch( void )
  {
    int &junk = free_items();
    if (&junk < 1)
    {
      say("I'm full!  I can't pick up anything else.", 1);
      return;
    }
    add_item("item-nut",438, 19);
    ...
  }

  See also:
    add_item(), add_magic() to add an item to the pack;
    count_item(), count_magic() to count how many of an item Dink is carrying;
    kill_this_item(), kill_this_magic(), kill_cur_item(), kill_cur_magic()
      to remove items;
    "Part 4: How Items Work" for general information.


#freeze()
# Category: Cut Scene
# Prototype:
#   void freeze( int sprite );

  This sprite now cannot move on its own.  Can still be killed though.

  See also:
    unfreeze(), load_screen()      [to reverse the effect of freeze()]
    say_stop(), wait_for_button()  [commands where freeze(1) is needed]
    sp_nocontrol()                 [alternative?]


#function reference -- see entry "my_proc()"


#game_exist()
# Category: Undocumented
# Prototype:
#   int game_exist( int slot# );

  Checks for the existence of save file SAVE#.DAT, where "#" is the value of
  the <slot#> argument.  Returns 1 if the save file exists, 0 otherwise.

  Example, from ESCAPE.C as released by RTSoft:
    // ... choice() code based on "&savegameinfo" pseudo-variables; then...
    if (game_exist(&result) == 0)
    {
      unfreeze(1);
      wait(2000);
      Say("Wow, this loaded game looks so familiar.", 1);
      kill_this_task();
    }

  See also: save_game(), load_game()


#get_burn()
# Category: Undocumented
# Prototype:
#   int get_burn( void );

  [This command was discovered by ReDink1: "Always seems to return 1.
  Might be related to different 'burn' versions of the CD... but overall
  a worthless command."]


#get_last_bow_power()
# Category: Weapon
# Prototype:
#   int get_last_bow_power( void );

  For use with [activate_bow()], the weapons' script can create the arrow or
  whatever according to how well [the player] shot it.

  [In other words, it returns the value of activate_bow()'s counter, which is
  based on how long the Ctrl key is held down.]

  See also: activate_bow()


#get_rand_sprite_with_this_brain()
# Category: Sprite
# Prototype:
#   int get_rand_sprite_with_this_brain( int brain, int sprite_to_not_count );

  Same as [get_sprite_with_this_brain()] but returns a random sprite #.  Used
  by dragons to target the town folk.

  [If you think about it, since there is no get_NEXT_sprite_with_this_brain()
  command, this command makes get_sprite_with_this_brain() pretty much
  superfluous...]

  See also: get_sprite_with_this_brain(), sp_brain()


#get_sprite_with_this_brain()
# Category: Sprite
# Prototype:
#   int get_sprite_with_this_brain( int brain, int sprite_to_not_count );

  This returns the first sprite # that is on the screen with this brain. It
  will not include the sprite # sent in the second parm in the search, so a
  sprite can check for 'other brains of its type' if needed.

  [This document originally also included an entry for a function,
  "get_last_sprite_with_this_brain()".  The description was:

     "Helpful with screenlock technology or any instance when you want to
     know when all enemies are dead, or you want to target an enemy of a
     certain brain type.

     "The 'sprite_to_not_count' parm is usually &current_sprite, it ignores
     this sprite [when] doing its computation.  Use 0 otherwise.

     "Check en-pill1.c for example usage."

  Well, there is no such function as get_LAST_sprite_with_this_brain() and
  en-pill1.c does not use it.  Here is the example code:

     if (get_sprite_with_this_brain(9, &current_sprite) == 0)
     {
        //no more brain 9 monsters here, let's unlock the screen
        screenlock(0);
        playsound(43, 22050,0,0,0);
     }

  There is also no direct way to process *all* the sprites with a given brain,
  such as to count them or make them all say() the same thing or whatever,
  since there is no function get_NEXT_sprite_with_this_brain().  Two is the
  most you can do:

     int &monster1 = get_sprite_with_this_brain(9, 0);
     int &monster2 = get_sprite_with_this_brain(9, &monster1);
     if (&monster2 > 0)
     {
       say("How many more of these do I have to fight?", 1);
     } else
     {
       if (&monster1 > 0)
       {
         say("Finally! Down to my last one!", 1);
       } else
       {
         say("Yippee! Got 'em all!", 1);
       }
     }

  Beyond that, you have to do something tricky:

     int &count9 = 0;
     int &this9;
  loop1:
     &this9 = get_sprite_with_this_brain(9, 0);
     if (&this9 > 0)
     {
       &count9 += 1;
       sp_brain(&this9, 89);
       goto loop1;
     }
  loop2:
     &this9 = get_sprite_with_this_brain(89, 0);
     if (&this9 > 0)
     {
       sp_brain(&this9, 9);
       goto loop2;
     }
     say("I see &count9 monsters!", 1);

  (end of get_sprite_with_this_brain() notes) ]

  See also: get_rand_sprite_with_this_brain(); sp_brain()


#get_version()
# Category: Undocumented
# Prototype:
#   int get_version( void );

  [ManaUser: "Returns dink engine version times 100. (e.g. 107 for 1.07).
  Obviously useful to warn the player that your d-mod needs features from
  a higher version than they have and won't work."]


#goto
# Category: Basic Syntax
# Example:
#   goto crap;
#     <skipping this part>
#   crap:

  Goto differs from C in one way:  The scope of a goto is the ENTIRE script.
  You can 'goto' other procedures.

  [This relaxation of C rules makes me shudder.  The right way to invoke a
  separate procedure is described under "my_proc()".  Unfortunately...

  ManaUser: "Sometimes it's *better* to use goto instead of a procedure though.
  This is because of some strange bugs with variables in DinkC which I couldn't
  really explain."

  Actually, ManaUser, I'm not sure this is a bug so much as a design limitation.
  See "my_proc()" for more explanation.

  BUT HERE IS A BUG NOTE: Be savvy about your constructs.  One script I
  corrected looked right, but it had a label inside an "if" statement's braces
  that was referenced by a "goto" in another proc.  When I rearranged the "if"
  statement so that it did a "goto" itself if the opposite condition was true,
  thereby placing the label outside of the "if", the bug went away...]


#hurt()
# Category: Sprite
# Prototype:
#   void hurt( int sprite, int damage );

  This lets you tell the game to hurt this sprite for a certain amount of
  damage.  [The sprite's defense will be figured into the calculation -- see
  ManaUser's note, below].  If the hit is for 0, there is a 50% chance that it
  will hit the [sprite] for 1.  (This way there are never any "I'm completely
  stuck" situations unless we purposely code them.)
     [That parenthetical comment is pretty inscrutable.  This command is
  actually rarely used.  Except for dragon scripts, injuries in normal combat
  are inflicted by the combatants' attack strength or touch damage, not by
  hurt() commands.  Perhaps Seth is referring to a hypothetical attack or
  spell for Dink that could be based on this command.]

  Also: If the thing is hurt, blood spurts are added, free of charge.

  [ManaUser: "One note on this, the damage isn't random like in normal combat,
  it's simply the number you specify minus the sprite's defense."
     It is also worth noting that a sprite's "nohit" property is ignored by
  hurt() -- such a sprite can still be hurt or killed.  Hurt() cannot kill a
  sprite with zero(0) hitpoints, but you still get the blood spurts and its
  hit() procedure is run.

  ReDink1 notes that if "damage" is a negative number, the game will freeze.]

  See also:
    sp_hitpoints() [for another way to change a sprite's hitpoints]
    sp_defense()   [for a description of the interaction between a sprite's
                      attributes when it is hit or otherwise hurt]
    sp_nohit(), sp_strength(), sp_touch_damage()


#if
# Category: Basic Syntax
# Prototype:
#   if ( value1 operator value2 )
#   {
#     <statement(s) to execute if condition is true>
#   }
#   else
#   {
#     <statement(s) to execute if condition is false>
#   }

  [Rules:
   (1) "value1" is typically a variable but in some cases may be a command,
       subject to "bug alert (2)" -- more discussion on this below.
   (2) "operator" may be: == (equal)
                          != (not equal)
                          >  (greater than)
                          >= (greater than or equal)
                          <  (less than)
                          <= (less than or equal)
   (3) "value2" is typically a literal value or another variable.  Other
       combinations of "value1" and "value2", such as "if (1 < &var)...",
       have not been tested and may or may not be valid.
   (4) Both "operator" and "value2" may be omitted.  As in standard C, these
       two constructs are identical:
         if (&var) ...
         if (&var != 0) ...
   (5) The braces may be omitted if only a single command is to be executed
       conditionally, subject to "bug alert (1)", below.
   (6) If braces are used (recommended), the only guaranteed bug-free construct
       is for every brace to appear on a line by itself, except that "else" may
       safely appear on the same line as its preceding close brace, if desired.
   (7) The whole "else" unit is, of course, optional.
   (8) If's may be nested to any desired level.  That is, the statement or set
       of statements in either the "if" or "else" unit may include another "if".
       In this case, the use of braces is HIGHLY recommended except possibly on
       the innermost "if".
   (9) There is no way to code an "and" or "or" construct.  To "and" conditions,
       use nested "if" statements.  To "or" conditions, see "Coding logically
       ORed conditions" in the examples below. ]

  Examples [as found in Seth's original document]:
    if (&life > 5) &life = 0;  // [BUG ALERT (1)]
    if (random(10,1) != 1)     // [BUG ALERT (2)]
       {
        say("The number is not 1!",1);
       } else
       {
        say("The number IS 1!",1);
       }

  [If] works like a standard [DinkC] if statement [which is mostly like
  standard C only different.  For example, consider this construct:

      if (&a == 0)
      {
         <do this if a is 0>
      } else
      if (&a == 1)
      {
         <do this if a is 1>
      }

  The above will, in C (and without the &'s), do what's in the first set of
  braces if &a is 0, or what's in the second set of braces if &a is 1, or
  nothing if &a is neither 0 nor 1.  DinkC, if and only if &a is 0, will do
  what's in BOTH sets of braces.  If an 'else' contains another 'if',
  explicit braces must be used:

      if (&a == 0)
      {
         <do this if a is 0>
      } else
      {
         if (&a == 1)
         {
            <do this if a is 1>
         }
      }

  Do NOT get creative with brace placement.  The so-called "one true brace
  style", for example, is unreliable.  An open brace is NOT recognized
  following "else", and sometimes but not always after an "if".  This
  common C brace style must NOT be attempted in DinkC:

      if (&a == 0) {
         <do this if &a is 0>
      } else {
         if (&a == 1) {
            <do this if &a is 1>
         } else {
            <do this if &a is neither 0 nor 1>
         }
      }

  BUG ALERT (1):
    Seth's example construct...
        if (&life > 5) &life = 0;
    DOES NOT ALWAYS WORK.  If you want to assign a value to a variable
    subordinate to an "if", the use of braces is the most reliable way:
        if (&life > 5)
        {
           &life = 0;
        }
    Without the braces, statements following the "if" may not be run.
        if (&life > 5)
           &life = 0;
        &anothervar += 1;                     // if the above statement is
        say("whatever...", &current_sprite"); //   skipped, so might these be

    EXCEPTION: if the "if" and the variable assignment is the only thing
    within a set of braces, it seems to work fine:
        if (&life > 5)
        {
           if (&strength > 4)
              &tuffenuff = 1;
        }

    This bug applies only to assignment statements subordinate to an "if".
    Other commands are ok:
        &loop_counter += 1;
        if (&loop_counter < 10)
           goto loop;
        say("all done...", 1);
        if (&found_it > 1)
           say("I found &found_it items", 1);
        else
           say("no items found", 1);

  BUG ALERT (2):
    Seth's example construct...
        if (random(10,1) != 1)
    where the returned value from a built-in function is tested directly by an
    "if", does indeed work in the case of random() but not for all functions.
    ManaUser alerts that it does NOT work for busy(), for example.  Not all
    functions have been tested.  It is (almost) always safest to assign the
    output of a function to a local variable first:
        int &ninety_pct_odds = random(10,1);
        if (&ninety_pct_odds != 1) ...

  Coding logically ORed conditions:
    Since there is no "logical OR construct in DinkC, it is usually best to
    code a series of "if" statements that "goto" a common "if true" routine
    or fall through to a common "else" routine:
        if (&rings >= 3)
           goto foundgenie;
        if (&lamps >= 1)
           goto foundgenie;
        // common "else" routine if both conditions are false; then
        goto skiptrue;
      foundgenie:
        // common "if true" routine if either condition is true; then
      skiptrue:
        // code to do regardless of above if's

   Sometimes, however, the necessary conditions are convenient.  For example,
   to do something if &story is 4 or 5:

      if (&story >= 4)
      {
        if (&story <= 5)
        {
          // do this if &story is 4 or 5
        }
      }

  (end of inserted "if" comments) ]


#init()
# Category: (none defined)
# Prototype:
#   void init( char line[100] );

  Lets you do a line normally done in the dink.ini file.  I use this to
  'replace' graphics on the fly.  (load new graphics to already used
  sequences, for dink's weapons)

  Example:  init("load_sequence graphics\milli\mill- 45 100");

  [Since the last update, I have had opportunity to study Dink.ini in depth,
  though I will not further bloat this document with what little I learned
  that is not already well covered on other documents on the Dink Network
  ( http://rpgplanet.com/dink/ ): follow the "Development" link and have a
  look at "D-Mod Dink.ini Editing v1.00" (Dan Walma) and "Dink.ini Index"
  (Gary Hertel).

  With respect to this command, note for example that the sequences of Dink
  walking and fighting without a sword are the very same sequence numbers as
  the ones that show the sword.  So by rights, all items -- at least all
  weapons -- that Dink can arm should include blocks of
  'init("load_sequence_now...");' commands.  Copy the ones for him without
  a sword from item-fst.c as released by RTSoft; the ones with a sword from
  item-sw1.c.

  I also have a note from Tyrsis referring to this block of comments in
  dink.ini:

  //this is here for reasons to complex to explain.  j/k!  This sequence
  //is one that is going to be replaced, so I'm loading the one with the most
  //frames first, because max frame count is initted only the first time we
  //load into a certain seq #.

  This comment implies that sequences may have differing numbers of frames
  and still replace each other as long as the longest sequence is loaded first.
  This is indeed true in the case of Dink's fist-hit sequences, 5 frames each,
  and his sword-hit sequences, 6 frames each.  Tyrsis warns, however, that it
  does not work well if the sequences are loaded from discrete bitmaps. If
  sequences are of differing lengths, be sure to create an ".ff" file using
  ffcreate or (preferably) the ffcreate tool in WinDinkedit.  Otherwise, the
  shorter sequence will be displayed with extra frames taken from some other
  available graphic.

  Dan Walma, alias ReDink1, also sent me sent me this note: "There is a slight
  bug that isn't mentioned anywhere, but I stumbled on it and was stuck for a
  few hours... This will not work:

     init("load_sequence_now graphics\elemonst\air\bonca\f03w1-  891 75 55 ...
  But this will:
     init("load_sequence_now graphics\elemonst\air\bonca\f03w1- 891 75 55 ...

  Note the two spaces in the first example.  It seems that init lines, like
  math, don't like any deviation from normal spacing at all."]


#initfont()
# Category: (Text Sprite)
# Prototype:
#   void initfont( char string[200] );

  Dink will change to this font.  Default is Arial.  You can change fonts as
  many times as you want.  This was included so international translations
  would be easier.

  See also: say() commands


#inside_box()
# Category: (none defined)
# Prototype:
#   int inside_box( int x, int y, int left, int up, int right, int down );

  Is the x and y cord inside this box?  Returns 1 if yes.

  [Said another way, this function's arguments are three x,y coordinate pairs.
  "left" and "up" are the x,y of the upper left corner of a box, and "right"
  and "down" are the x,y of the lower right corner.  This function will return
  1 if the first x,y pair is in the box defined by the second and third pairs.
  For example:

      int &x = sp_x( 1, -1 );
      int &y = sp_y( 1, -1 );
      int &in_box = inside_box( &x, &y, 200,150, 400,300 );

  If Dink (that is, if Dink's "depth dot") is in the box implied by 200,150 as
  the upper left corner and 400,300 as the lower right, the above will set
  &in_box to 1, otherwise 0.]

  See also: sp_x(), sp_y()


#int
# Category: Basic Syntax, Variable Definition
# Examples:
#   int &junk;
#   int &crapvar = 0;
#   int &num = sp(12);
#   int &nummy = random(100,1);
#   int &wklvl = &level;

  The int statement creates a variable accessible ONLY to the current
  instance of the current script.  It differs from C in one way:  The scope
  of an int in DinkC is the ENTIRE script.  Any procedure in the script can
  see/use this var.  [NOT TRUE.  Procedures invoked as a function reference
  cannot see the script's other local variables.  See "my_proc()" entry.]

  [Notes:
   (1) If a variable does not exist at the time it is referenced in a script,
       its value is zero(0).
          if (&nosuch == 0)
          {
            // statements here will always be executed if &nosuch was
            // never defined.
          }
   (2) Note (1) cannot be relied on, however -- the script interpreter may
       also just choose to discard any line with a non-existent variable.

   (3) Alternately, if the variable already exists, the "int" itself is
       simply ignored and the rest of the statement works fine.  For example:

       loop:
          wait(50);
          int &dinks_y = sp_y(1, -1);
          if (&dinks_y < 50)
            say("I'm getting too close to the edge...", 1);
          goto loop;

   (4) Be careful not to name int variables either the same as global or as
       a global's name with additional characters.  See the "variable
       definition" category description, and make_global_int() note (2), for
       more discussion.

   (5) All make_global_int() variables and all "int" variables of all running
       scripts share a single pool of 248 slots for variables.  Therefore, it
       helps to be conservative in the use of variables:
        - define them sparingly, re-using variables when it can be done safely;
        - define them later in the script when it makes sense.  For example, if
          the defining of a variable can be put off until the "die()" proc of a
          monster brain, the variable uses no slots until the script is almost
          ready to be discarded from memory.
        - use kill_this_task() to kill scripts no longer needed.]

  See also:
    make_global_int()           [to define variables available to all scripts]
    "Variables" category header [for more notes about variables]


#is_script_attached()
# Categories: Script Management; Sprite
# Prototype:
#   int is_script_attached( int sprite# );

  Returns the script id # in memory attached to the sprite.  0 if none.
  dam-fire.c [script for fireball magic] uses to determine if a tree has some-
  thing 'special' going on with it. (so it doesn't just burn it like normal)

  [run_script_by_number() is the only real use for a script number, but it's
  an important use.
     This is the only available command for determining the number of a script
  which is already running, but two other commands return a script number when
  a script is started: sp_script() and spawn().]

  See also: run_script_by_number(), sp_script(), compare_sprite_script()


#kill_all_sounds()
# Category: Sound
# Prototype:
#   void kill_all_sounds( void );

  Kills all "survive" sounds, good if you don't want to keep track of the
  sound # forever.  Does not affect regular sounds, so essentially it's a
  badly labeled procedure.  I could probably have renamed it in the [time]
  it took me to explain this.

  See also: sound_set_survive(), sound_set_kill()


#kill_cur_item()
#kill_cur_magic()
# Category: Inventory, Weapon
# Prototype:
#   void kill_cur_item( void );
#   void kill_cur_magic( void );

  [Kill_cur_item() deletes the currently-armed weapon, and kill_cur_magic()
  the currently-armed magic, running its script's holdingdrop(), disarm(),
  and drop() scripts (in that order).

  According to the Transfer dmod, part of the Catacombs dmod by Paul Pliska
  (whom I now know to be ManaUser), kill_cur_item() "has a side effect of
  acting like return."  Actually, he's wrong: it acts like kill_this_task()!
  He solved this problem by making kill_cur_item() the only command (besides
  a kill_this_task() for safety -- NEVER rely on a bug!) in an external
  script's main() procedure and invoking it with spawn().  Here is the heart
  of a kill_cur_item() routine, based on his:
     spawn("delweapn"); // kill_cur_item(), + kill_this_task() just in case
     wait(1);           // let spawned script run
     draw_status();     // update status area on playing screen
     say("Boy, am I glad to be rid of that!", 1);

  All this applies equally to kill_cur_magic().]

  See also:
    kill_this_item(), kill_this_magic() to remove items by script name;
    "Part 4: How Items Work" for general information.


#kill_game()
# Category: (none defined)
# Prototype:
#   void kill_game( void );

  Yup, like it sounds.


#kill_shadow()
# Category: Weapon
# Prototype:
#   void kill_shadow( int sprite );

  Checks for sprites with a brain of 15 [projectile's shadow -- see function
  sp_brain() for more information] and [an sp_brain_parm()] of 'sprite' and
  kills them.  I use this to remove shadows [under] projectiles when the
  projectile explodes but isn't killed.  (Shadows kill themselves when the
  sprite they were attached to dies.)

  [Example, modified extract of dam-fire.c, the script invoked when a fireball
  hits something:

     void damage( void )
     {
        playsound(18, 8000,0,0,0);  // sound effect
        &scrap = &current_sprite;   // copy &current_sprite, reason unknown
        kill_shadow(&scrap);        // kill just the fireball's shadow
        sp_brain(&scrap, 7);        // leave fireball alive, but change it
                                    //   from a missile brain (brain 11) to
                                    //   a play-animation-once brain
        sp_seq(&scrap, 70);         // specify animation sequence to play
        sp_pseq(&scrap, 70);        //    "        "        "     "   "
        sp_frame(&scrap, 1);        //    "        "        "     "   "
        ...
     }


#kill_this_item()
#kill_this_magic()
# Category: Inventory, Weapon
# Prototype:
#   void kill_this_item( char name_of_item_script );
#   void kill_this_magic( char name_of_item_script );

  Kills first instance of this item by name of script: use kill_this_item()
  to delete an item added by add_item(); kill_this_magic() for one added by
  add_magic().  If it's currently armed, it will run its [holding_drop(),]
  disarm() and drop() [procedures], otherwise it will just run drop().
  [Either way, the item is then removed from the inventory.]

  [It is not necessary for the named script to actually exist.  As long as the
  same script name is used as was specified by add_this_item()/magic(), the
  item will be removed.]

  See also:
    add_item(), add_magic() to add an item to the pack;
    kill_cur_item(), kill_cur_magic() to kill the currently-armed item;
    "Part 4: How Items Work" for general information; and
    kill_this_task() for more notes on writing item scripts.


#kill_this_task()
# Category: Script Management
# Prototype:
#   void kill_this_task( void );

  Kills the current script completely.  This is rarely [needed], because if a
  script is attached to a sprite, [the script] will die [automatically] when
  the sprite does.  ESCAPE.C uses this because it isn't attached to anything.
  ([ESCAPE.C] is run when the ESCAPE button is pressed.)

  [Notes:
   (1) It has to be written "kill_this_task()" in a script to be effective.
       The parentheses are *required*.  The command "kill_this_task" is 
       discarded and ignored, as is "killthistask" with or without
       parentheses.  The semicolon, however, is optional.  When in doubt,
       use a scripts_used() script, such as described under that command,
       to tell whether the script is being killed as appropriate.
   (2) Beware of overusing this command.  It should not be used to kill an
       ordinary script attached to a sprite, for example.  If Dink subsequently
       interacts with that sprite, the script will be reloaded, its main()
       will be run again, and the whole process can cause unexpected things
       to happen during game play.
   (3) On the other hand, there is reason to disagree with Seth's statement
       that kill_this_task() "is rarely needed."  It must in fact be used in
       the following contexts:
         - scripts attached to keys, including "key-##", "button6", and
           "escape";
         - scripts invoked by spawn();
         - scripts that do a script_attach(1000);
         - scripts that do a force_vision(); and
         - the following procedures of "item" scripts (that is, scripts named
           in add_item() or add_magic() commands -- these are conventionally
           named in the pattern "item-???", but this is not a requirement):
             . pickup()
             . disarm()
             . drop()
           Note that the above three procedures *must* be included in all
           "item" scripts, if for no other reason than to kill_this_task(), in
           order to avoid stray scripts from remaining in memory after they are
           no longer needed.

  Failure to use kill_this_task() when appropriate is to risk running out of
  what has been determined to be an extremely finite resource: there are only
  248 slots available for use by all global variables plus all the "int"
  variables of all running scripts.  See the "Variables" category header for
  more information.]

  See also: force_vision, script_attach(), scripts_used(), spawn();
    "** What's new in 1.06? (10-19-99) **"  [key-## script discussion]
    "Part 4: How items work "               [functions in item scripts]


#load_game()
# Category: Undocumented
# Prototype:
#   void load_game( int slot# );

  [Replaces all game-state information with information from file SAVE#.DAT,
  where "#" represents the value of the <slot#> argument.  This command is a
  rare exception to the rule that the dink engine will look under the main
  game's folder "dink" for files not found in a dmod's folder: the SAVE#.DAT
  file must appear in the dmod's own folder.

  The typical dmod has, or at least has access to, three load_game() commands:
  (1) start-2.c, which drives the "Continue" button or its counterpart on the
  dmod's opening screen; (2) escape.c, run when the player presses the Escape
  key; and (3) dinfo.c, Dink's die() procedure.

  ALL running scripts are cancelled by load_game().  Period.  Even if the load
  fails.  It is unclear what happens in that case, but the script that did the
  load_game() does not continue running no matter what.  Use game_exist() first
  so that a failed load can be anticipated and prevented.

  Dinfo.c, as released by RTSoft, does contain this curious bit of code:
    script_attach(1000);
    //script now can't die when the load is preformed..
  Start-2.c has similar lines:
    set_mode(2);
    //script now can't die when the load is preformed..

  Both commands are exercises in futility and the comments are, unfortunately,
  lies (depending on what it means to pre-form a load instead of perform
  one...)  Sometimes it would be great to be able to do some follow-up after a
  load_game(), to check the loaded global variables to see if this save is from
  the current version of the dmod or whether a constantly-running script should
  be spawned, but that is just not possible.  The best you can do is put code
  in the main() procedure of your savebot that will do this follow-up for you.

  Usually, the slot# argument is &result, as set by a choice() based on
  "&savegameinfo" pseudo-variables.

  As stated, all currently-running scripts are cancelled by load_game(); but
  these are started to replace them: (1) the base script for the &player_map
  screen; (2) all the main() procedures of all the scripts attached to any
  sprite on the screen; and (3) the disarm() and then the arm() procedures
  for Dink's currently armed weapon and/or magic, if any.]

  See also: save_game(), game_exist();
            "Part 3: A Changing World"
                [for more information about what is included in save files]
            "Part 5: How the Choice Statement Works"
                [for more information on "&savegameinfo"]


#load_screen()
# Category: Cut Scene, Screen Drawing
# Prototype:
#   void load_screen( void );

  Loads screen [the screen specified by &player_map.]

  [Notes:
   (1) This command seems to load fresh copies of any scripts attached to
       the screen or its sprites, but does not run them -- that is done by
       draw_screen()
   (2) A freeze(1) command is no longer in effect after load_screen().  A
       corresponding unfreeze(1) is unnecessary unless the freeze(1) command
       is re-issued after the load_screen().
  (end of load_screen() notes) ].

  See also: draw_screen(), draw_status(), "Appendix B: warp-eg.c";
            sound_set_survive()


#load_sound()
# Categories: Sound; Undocumented
# Prototype:
#   void load_sound( char wav-name[12], int sound# );

  [So far as I know: the purpose of load_sound() is to read a .wav sound-effect
  file and associate a number with it for use by playsound() and sp_sound().
  It is my understanding that <sound#> must be in the range 1-99, allowing up
  to 99 sounds in memory at any given time.  This command is used primarily
  in Start.c to initially load the game's sounds.  Example from Start.c as
  released by RTSoft:

     load_sound("QUACK.WAV", 1);
     load_sound("PIG1.WAV", 2);
     load_sound("PIG2.WAV", 3);
     ...
     load_sound("AXE.WAV", 48);
     load_sound("BIRD1.WAV", 49);

  It is also my understanding that the sound associated with a number may be
  changed as the game progresses by load_sound() commands in other scripts,
  but this is in fact rare and I see no examples of it.

  Sylvain Beucler says that this is one of a number of commands where "the
  argument is not the filename, but a relative path, and it is not limited
  to 12 characters."  It would be valid to divide a dmod's "sound" directory
  into subdirectories and then use commands like this:

     load_sound("pignoise\pig1.wav", 2);

  A full "filespec" beginning with a drive letter is not valid, but "relative
  paths," ones that include the ".." parent-directory symbol, are valid.  Since
  this command expects to load from a dmod's "sound" directory, the parent
  directory is the dmod directory, and its parent is the Dink Smallwood install
  directory.  Thus it is possible to load sounds directly from another dmod,
  though this is probably not recommended except perhaps from "Mystery Island":

     load_sound("..\..\island\sound\robohit.wav", 2);

  (end of load_sound() comments]

  See also: playsound(), sp_sound()


#make_global_int()
# Category: Variable Definition
# Prototype:
#   void make_global_int( char nameofvar[20], int default_value );

  [Defines] a global int - the default_value is only [applied] to it if this
  is the first time it has been initialized and doesn't exist already in the
  saved game file. (usually these [make_global_int statements] are in the
  main.c file.)

  [Examples, from main.c as released by RTSoft:]
       make_global_int("&strength", 3);
       make_global_int("&story", 0);
       make_global_int("&old_womans_duck", 0);

  [Notes:
   (1) See category definition for variable name rules and restrictions.

   (2) This command is generally only used in main.c.  Mike Snyder's comment
       in his (great!) skeleton dmod, "//Add your own globals here.  Remember,
       you only get about 200 of them total, so be sure you don't go overboard",
       should be treated as a "rule of thumb."  The limit is 248 total globals
       LESS the number of slots you want to reserve for ALL local variables in
       ALL instances of ALL scripts that can be running at any given moment.
       A dmod that defines 200 globals is likely to have trouble with some
       scripts not being able to define all their local (int) variables.

   (3) Be SURE to keep your names straight.  If you try to create a local
       variable (see "int") that has the same name as a global variable -- or,
       said another way, if you create a global variable that has the same name
       you used for a local variable somewhere -- all references will be to the
       global variable.  For safety, avoid creating globals with working-
       variable-looking names like &temp, &scrap, etc. -- I've seen this done,
       and it can introduce some nasty bugs.  You may want to create a naming
       convention that makes sense to you, such as to make all local variable
       names begin with "&_" (&_junk, &_temp, etc.)

   (4) I have read recommendations that all user global int's should be
       assigned their initial values in Start-1.c, the script for the New Game
       button, in case the dmod has been partially played and the user is
       starting over.  While that makes sense to me, "ManaUser" writes: "This
       doesn't seem to be an issue, main.c is re-run every time a new game is
       started or loaded so it should be perfectly safe to rely on variables
       starting at the default value.  I just tested it and indeed it seems
       to work fine."  Somehow, that does not surprise me, even though Seth's
       description above specifically says the default value is only applied
       if the variable doesn't exist already, which it would on Restart.
       Nevertheless, the Start-1.c recommendation does seem to apply only
       to a pre-1.06 release of Dink.]

  See also: int


#move()
# Category: Cut Scene
# Prototype:
#   void move( int sprite, int direction, int destination, bool nohard );

  This sprite will move in this direction (can be 2, 4, 6 or 8) [numeric
  keypad directions] until sprite's X or Y (depending on dir) meets or exceeds
  'destination' [*].  If nohard is 1 then the sprite can walk through hardness,
  if 0 it will get stuck on walls like normally.

  [*] I think this means:
      if direction=2 (down),  move until sprite's Y >= destination
      if direction=4 (left),  move until sprite's X <= destination
      if direction=6 (right), move until sprite's X >= destination
      if direction=8 (up),    move until sprite's Y <= destination

  ManaUser: "not EXACTLY though, it may go over by a couple pixels."  Which is
  just what should be expected.  Sprites are not usually moved only one pixel
  at a time [see sp_mx() and sp_my()], so the odds are against a full move
  reaching "destination" exactly.

  ManaUser: "You can also make a sprite move diagonally, (1, 3, 7 or 9), in
  which case <destination> is the X coordinate it should stop at.  So if sprite
  13 is at 100,200 and you go
      move(13, 3, 150, 1);
  it would stop at 150,250" (subject to his above overrun warning).

  This is a great tip.  Making sprites move diagonally can add a lot of logic
  to their choreography, but it makes the movements much more realistic.]

  See also:
    move_stop()       [another move command]
    sp_x(), sp_y()    [to relocate a sprite instantly instead of moving it]
    draw_hard_map()   [if a sprite with hardness is moved]
    sp_speed()        [to control speed of move]
    sp_mx(), sp_my()  [referenced in above notes]


#move_stop()
# Category: Cut Scene
# Prototype:
#   void move_stop( int sprite, int direction, int destination, bool nohard );

  Same as above but stops script until the sprite has met the destination.  Use
  carefully, if the sprite hits something, it will NEVER get to the destination
  and thereby the script will never be finished.  Won't cause a game crash or
  anything, though.

  [Whatever "anything" means.  I can't recall what (otherwise great!) dmod it
  is that suffers from this bug:  If Dink enters a forbidden town, a cut-scene
  ensues where Dink walks up to a town official for a stern warning. The scene
  works fine if Dink walks in the town gate.  Unfortunately, he can also enter
  the same screen from the other side of a fence; and in that case, Dink gets
  as far as the fence and the game locks.  It's true that neither Dink.exe nor
  Windows crashes; but the only thing you can do is Alt-Tab out of Dink, then
  bring up the Ctrl-Alt-Delete window and do an End Task on Dink Smallwood.
  And since this seems to leave Dink-initiated processes running, the best
  thing to do next is restart Windows.
     If I find which dmod it is, I now know that the way to fix the problem
  is to use inside_box() to test and see if Dink is in the outside-the-fence
  area, and, if so, do something different (or nothing special at all).

  ManaUser offers this suggestion: "I just set <nohard> to 1 all the time
  unless there is some reason not to. It's quite a bit safer since the worst
  you risk is someone looking funny by walking through a tree."
     I agree that "looking funny" is immeasurably preferable to locking up the
  game, and I recommend doing the same thing with <nohard>.  However, I regard
  the tactic as a safety precaution, not the correct solution for a cut-scene
  such as the one described above.]

  See also:
    move()            [another move command]
    sp_x(), sp_y()    [to relocate a sprite instantly instead of moving it]
    draw_hard_map()   [if a sprite with hardness is moved;
                         example of move_stop() in push-rock routine]
    sp_speed()        [to control speed of move]


#my_proc() [user-defined function reference]
# Category: Basic Syntax
# Example:
#   my_proc();

  This is how you run a procedure located in the current script.  [It's the
  primary way, anyway -- set_callback_random() is another way.]

  [WARNING: It is difficult to create meaningful user-defined procedures in
  DinkC:
    (1) values cannot be passed to user-defined procedures;
    (2) as noted in "return", user-defined procedures cannot return values;
        and
    (3) to a procedure invoked this way, local, "int" variables in the rest
        of the script are out of scope!

  Consider this example key-## script:

     void main( void )
     {
        int &myvar = 11;
        my_proc( 22 );
     }
     void my_proc( int &passval )
     {
        say("my_proc: myvar=&myvar passval=&passval story=&story", 1);
     }

  Name this script, say, key-45.c and press the Insert key.  Dink will confirm
  that my_proc() gets invoked, but that variables &myvar and &passval are
  unknown.  Only the value of the global variable &story is available to the
  function.  But if you get into the proc using a goto...

     void main( void )
     {
        int &myvar = 11;
        goto my_proc_label;
     }
     void my_proc( int &passval )
     {
     my_proc_label:
        say("my_proc: myvar=&myvar passval=&passval story=&story", 1);
     }

  ...&myvar remains in scope in this case.  But then again, there is no need
  for the extra function header when goto is used.

  To be sure, a user-defined procedure may create and use its *own* "int"
  variables, but their values are not available to the calling procedure.
  For a procedure's own local variables and a subordinate procedure's local
  variables to be out-of-scope to each other is certainly in keeping with
  the philosophy of procedural languages, like DinkC tries to be.  But the
  lack of any means except global variables for DinkC procedures to
  communicate with each other makes this a serious design limitation.

  Additional information: this design limitation is shared by external().  A
  script could use external() to run another proc within itself; unfortunately,
  external() temporarily creates a new instance of the script it calls, and
  that instance's variables are not shared with this instance.  It is worth
  noting that run_script_by_number() does not create a new instance of the
  script.  This fact may hold the key to overcoming this or other limitations.]

  See also:
    external(), spawn(), run_script_by_number
                                 [to invoke functions in other scripts]
    goto, set_callback_random()  [for other ways to invoke code in this script]
    return                       [for related information]


#playmidi()
# Category: Sound
# Prototype:
#   void playmidi( char midiname[12] );

  [Plays file "midiname" from the "sound" folder of the dmod or "dink".
  Example -- script sc-end.c released by RTSoft:

     void main( void )
     {
     //Entering the secret snow caslte....
     playmidi("caveexpl.mid");
     }

  ManaUser: "The numbers 1002-1018 as a 'name' have special meaning.  They
  play the audio tracks on the Dink Smallwood CD.  All but 1003, 1008, 1014,
  1015 and 1017 have MIDI alternatives for those people (the majority) using
  the freeware version without the original CD in the drive.  1001 and 1020
  also play MIDIs if the Dink CD is not present, although with the CD, 1001
  has no effect (since track 1 is the data track on the CD) and 1020 stops
  the music (since the CD has no track 20).  These numbers also work for
  screens (press M in DinkEdit).  Lastly, it's interesting that it will
  confuse certain audio CDs (such as 'O Brother, Where Art Thou?' soundtrack)
  with the Dink CD."
     I am among the majority with no Dink Smallwood CD, so I can only vouch
  for part of this.  Follow-up with ManaUser and experimentation produced
  these examples:
     playmidi("1001");
        // without the CD, plays "1.mid" (specified number less 1000);
        // with the CD, no effect -- will not try to "play" data track
     playmidi("1003");
        // plays track 3 of CD, or "3.mid" (if dmod has one)
     playmidi("1003.mid");
        // plays, as specified, "1003.mid"
     playmidi("1020");
        // with CD, does nothing (no track 20);
        // without CD, plays "20.mid" if found

  Sylvain Beucler says that this is one of a number of commands where "the
  argument is not the filename, but a relative path, and it is not limited
  to 12 characters."  It would be valid to divide a dmod's "sound" directory
  into subdirectories and then use commands like this:

    playmidi("classic\danube.mid");

  A full "filespec" beginning with a drive letter is not valid, and Silvain
  goes on to warn about crashes with playmidi() and "relative paths" that
  include a ".." parent-directory reference.  According to him, this problem
  is not shared with other commands such as spawn().]

  See also: stopcd(), stopmidi()


#playsound()
# Category: Sound
# Prototype:
#   int playsound( int sound#, int min_speed, int rand_speed_to_add,
#                  int sprite_or_0, bool repeat? );

  Plays a sound.  If rand_speed_to_add isn't 0, a random # will be made from
  this # and added to the sound.  If sprite isn't 0, the sound will be 3D
  based on this sprite's location.  If the sprite is killed for some reason
  ([e.g.], a fire goes out), the sound will die too.  If repeat is not 0, the
  sound will repeat.

  Returns the soundbank used.  Use this for special control of the sound.
  See: sound_set_vol()      [to reduce the volume of the played sound]
       sound_set_survive()  [to make the sound survive a screen change]
       sound_set_kill()     [to kill a sound set to "survive"]

  See also:
       load_sound()         [<sound#> argument is as set by load_sound()]


#preload_seq()
# Category: Animation
# Prototype:
#   void preload_seq( int sequence );

  Loads the graphic sequence if it isn't already cached somewhere. (Sequences
  are defined in DINK.INI)

  [ManaUser: "Rarely important; but if, for example, you want a wizard to vanish
  in a flash of fire, preload the fire.  Otherwise the delay from loading could
  make the fire go off after the wizard leaves, or something ugly like that."
     I have seen situations where clobbering a monster results in a noticeable
  delay before its dead body is shown.  Those few tenths of a second would have
  been far less noticeable if those graphics had been preloaded when the screen
  was first drawn, which is the tradeoff made by 99% of monster-brain scripts
  when they issue a series of these preload_seq() commands.

  A more important use of this command precipitates from Tyrsis' note, quoted
  under create_sprite().  Here is an example of the precise situation she
  describes, from the dmod, "9 Gems of Life, Part 2," by Jan Willem Veenhof and
  WolfBlitz.  This is a modified extract of their "puzzel3.c" script:

     // create a magical smoke ring:
     int &boom = create_sprite(314, 182, 7, 167, 1);
     sp_seq(&boom, 167);
     // create a scroll:
     int &scroll = create_sprite(314, 180, 0, 422, 5);
     playsound(24, 22052, 0, 0, 0);
     sp_script(&scroll, "get-df");

  Very similar code appears in two other scripts, puzzel1.c and puzzel2.c,
  where it works just fine; but in this case, when this code was run, I saw
  the smoke ring and heard the sound; I could send Dink through the area where
  the smoke ring was to pick up the scroll (that is, activate the touch()
  procedure in get-df.c); but the scroll itself was invisible.  Remembering
  Tyrsis' tip, I created this main() procedure:

     void main( void )
     {
       preload_seq(167);
       preload_seq(422);
     }

  End of problem.  The "preload_seq(167)" was just for good measure, since
  the smoke ring appeared; but it made sense to throw it in anyway.

  One more footnote: remember that "Please Wait" banner that sometimes appears
  when you go to move Dink onto a new screen?  The dink engine shows that when
  it is doing preload_seq() commands that actually have to be loaded. ManaUser:
  "but only if called from a base script (this info from Dan Walma)". ]

  See also: create_sprite()


#push_active()
# Category: Dink Specific
# Prototype:
#   void push_active( bool 1_or_0 );

  1 by default.  If 0, the human brain (usually Dink) will not allow
  'pushing'.  I use this when I turned Dink into a duck on Mystery Island.


#random()
# Category: (none defined)
# Prototype:
#   int random( int max, int plus_this );

  Random(10,1) would return a number between 1 and 10.  Random(5,0) would
  return a number from 0 to 4.  [Similar] to how C does it.

  [The use of "max" as the designator for the first number is misleading.
  The random() function is capable of generating any of "max" distinct integer
  values, in the range 0 to (max minus 1); then "plus_this" is added to the
  generated value to create the return value.  The clearest example is
  random(2,0), which can generate either of two values, 0 or 1.

  More succinct wording courtesy of ManaUser: "<max> is the number of
  possible values and <plus_this> is the lowest possible value."

  Also note that it is perfectly valid for <plus_this> to be negative.
  "Random(5,-2)", for example, generates 2, 1, 0, -1, or -2.]


#reset_timer()
# Category: (none defined)
# Prototype:
#   void reset_timer( void );
  
  Sets the person's time to 0, and starts the counter from now. (Done in
  start-1.c, when the guy starts a new game.)

  [I am guessing, and ManaUser concurs, that this "time(r)" is nothing more
  than the one shown in saved game information.  I have seen dmods that just
  show a strange number instead of a playing time in the saved games list;
  I need to find one again and check on what they do or don't do with
  reset_timer().  That would probably confirm my suspicions.]


#return
# Category: Basic Syntax, Variable Definition
# Syntax:
#   return;

  Exits the script.  Returning values, i.e. return(&var), is not supported.

  [Note that "return" causes the script to immediately become inactive but it
  remains in memory, ready to be activated again.  Consider this script, which
  might be attached to an NPC:

     void main( void )
     {
        if (&been_here_before)
           return;
        say("Hi, Dink! I've been expecting you.", &current_sprite);
        &been_here_before = 1;
        // ("return;" optional here but unnecessary)
     }
     void talk( void )
     {
        <whatever should happen when Dink talks to this sprite>
     }

  The first time Dink walks into this room, the NPC will speak up but then set
  a make_global_int() variable non-zero so the "return" gets executed on any
  subsequent visit.  But in either case, the script remains in memory after
  main() finishes so Dink can "talk" to the NPC.  Compare this to the effect
  of kill_this_task(). ]

  See also: my_proc()


#run_script_by_number()
# Categories: Script Management
# Prototype:
#   void run_script_by_number( int script id #, char proc_name[30] );

  For use with [is_script_attached()], basically.  You can force a script
  to run its DIE procedure, for instance.

  [Example: Here is a slightly modified extract of dam-fire.c:]
     int &junk = is_script_attached(&missile_target);
     if (&junk > 0)
     {
         run_script_by_number(&junk, "die");
         return;
     }
     // goes on to determine whether a "burnable" tree was hit, and to
     // burn it if so.

  The spawn() command also returns a script number, and I used it successfully
  to solve a very elusive problem: how do you know if a spawned script is still
  running?  It doesn't seem like it would be that hard until you remember that
  such scripts -- contrary to the dire warnings about them running forever --
  are killed without warning or mercy by load_game().  This works:

  In main.c:
     make_global_int("&running_script", 0);

  In the spawning script:
     &result = 0;
     run_script_by_number(&running_script, "answer");
     if (&result < 0)
     {
       // (stuff to do if the script is already running)
     }
     else
     {
       // (stuff to do if the script is not running, including...)
       &running_script = spawn("spawn-eg");
     }

  In "spawn-eg" ("eg" as in "e.g." -- Latin for "for example"):
    void main( void )
    {
      // (setup commands)
    mainloop:
      // (stuff to be done over and over until it's time to kill_this_task())
      wait(100);  // or whatever -- wait() is critical, duration is not
      goto mainloop;
    }

    void answer( void )
    {
      &result = -1;
      goto mainloop;
    }

  So the spawning script can use run_script_by_number() to call a possible
  prior invocation of the spawned script; and if it is still running, it will
  answer by setting &result to -1.  And it is "safe": the spawned script, if
  it is running at all, must be doing its wait() when another script calls it.
  So it does not have to be interruptible at any point, only at its wait()
  point(s).  The only, slight danger is if timing is important: there is no
  way for the answer() proc to know how much of the wait() had elapsed when
  it was interrupted.  It could have been after less than 1 millisecond,
  almost the full duration (100 in the above example), or anywhere in between.

  Note that the above calling script "knows" the name of the script it is
  trying to run by number.  So why doesn't it use external() to run it?  Well,
  in this case, of course, the intent is to know whether the script is active,
  not just whether a script by that name is available in the \story directory.
  But there is another important reason in other contexts: external() starts a
  new instance of a script; run_script_by_number() runs the existing instance.
  And the referenced procedure has access to all the other local variables in
  the script.  Just remember when writing a script you intend to reference this
  way: as far as the script is concerned, the run_script_by_number() is an
  unscheduled "goto" to the named procedure.  If the script has a loop intended
  to run all the time, any procedure intended for reference by
  run_script_by_number() must end with a goto back to the main loop.

  FOLLOW-UP -- BUG ALERT: ok, it's probably not a bug, at least not in the code,
  but it amounts to one anyway.  ReDink1: "I did notice one thing, though...
  if the engine can't find the specified procedure in the script, then it runs
  the die() procedure.  Odd, eh?"
     %$#@!!!  "Odd" is not the word I muttered when I read this.   This is the
  only way we've got to tell whether a script number still refers to the script
  we want.  If the script number is now unused, everything is still ok.  But if
  the script number has been reassigned, such as to a monster, things can
  start to act funny, like a heart or gold appearing where a monster is but
  the monster remains alive.  And if the script with that number has neither
  the requested proc nor die(), it seems like push() might be run.
     Confound it anyway.  Use with caution, I guess.  No reasonable defense
  against this extra processing comes to mind. ]

  See also: external(); is_script_attached(); spawn()


#save_game()
# Category: Undocumented
# Prototype:
#   void save_game( int slot# );

  [Writes a file SAVE#.DAT, where "#" is the value of <slot#>.  The file
  contains internal game-state information (such as Dink's current position),
  all global variables, and the editor_type/seq/frame values for all sprites
  on all screens.

  Usually, the slot# argument is &result, as set by a choice() based on
  "&savegameinfo" pseudo-variables, but this certainly need not be the case.
  ManaUser: "It can go higher than 9999, and can be zero or negative as well."
  There seems to be no problem whatever if the value is something like
  -123456789 which causes the filename to be longer than the DOS-standard 8
  characters.]
  
  See also: game_exist(); load_game();
            "Part 3: A Changing World"
                [for more information about what is included in save files]
            "Part 5: How the Choice Statement Works"
                [for more information on "&savegameinfo"]


#say()
# Categories: Sprite, Text Sprite
# Prototype:
#   int say( char string[100], int sprite );
#   // [prototype corrected from "char string [200]" in original document]

  Says the string above the sprite you wish.  Use &current_sprite to choose
  the 'owner' of the [displayed text] (assuming [the text] is attached to a
  sprite).
    If sprite was already talking, the old text will be erased. (so they
  don't overlap)
    If "sprite" is 1000, this will be a 'text only' say, not attached to a
  sprite.  If this happens, you can use the return int to set its x and y[*].
  1000 is used for cut scenes - it also turns off centering.

  If you need to know the SPRITE # of a certain text sprite, you can check
  &last_text at any time.

  [(*) Much of that explanation just left me scratching my head, but some
  comments from ManaUser finally turned on the lights.
   (1) "First of all, say() returns int, not void".  So noted, ManaUser -- the
       above prototype is now corrected.
   (2) So what does it return?  It becomes obvious when the function of say()
       is explained like this: say() dynamically creates a sprite, just like
       create_sprite() does. Only instead of giving it the pseq and pframe of
       a graphic, we specify some text for say() to turn into a "text sprite".
       So the return value of say() is "the sprite number of the text sprite"
       (ManaUser again).
   (3) Since, unlike create_sprite(), (and ignoring say_xy() for a moment), we
       don't give say() any X or Y coordinates, where does it put this newly-
       created sprite?  Well, if the <sprite> argument identifies an existing
       sprite, it tries to visually associate the new text sprite with the
       existing <sprite>.  Pretty slick.  But if <sprite> is 1000, you can't
       seem to control where say() puts it, and it may well give it coordinates
       like (-75,-75) that are off screen.  But here is one thing you can do
       about that:
          int &ts = say("This is a text sprite for sprite 1000", 1000);
          sp_x(&ts, 0);
          sp_y(&ts, 0);
       So say() will create the text sprite somewhere, who knows where, but
       then the sp_x() and sp_y() commands will instantly relocate it to the
       top of the screen: X,Y coordinates 0,0.
   (4) In spite of what Seth said, the above code causes the text sprite to
       be centered at the top of the screen.  Worse, (a) if you then do 
          int &x = sp_x(&ts, -1);
       variable &x will be set to 0 (because that's what we set the text
       sprite's x-coord to), not the centered location; and (b) if you change
       sp_x() to something like
          sp_x(&ts, 100);
       now the text sprite is simply displayed 100 pixels right of center,
       not at X=100.  However, at least it is valid to do something like
          sp_x(&ts, -100);
       to move the text sprite 100 pixels left of center.
   (5) While say() does return the sprite number of the newly-created sprite,
       it is not necessary to remember it -- it's also stored in &last_text:
          say("This is a text sprite for sprite 1000", 1000);
          sp_x(&last_text, 0);
          sp_y(&last_text, 0);
       I would suggest this is dangerous, however, if there is any chance that
       more than one script can be creating text sprites simultaneously.  Both
       would be updating &last_text at the same time, and it's just barely
       possible that &last_text could change between the say() and the sp_x().
       This is the stuff of which random, irreproducible bugs are made.
   (6) And, of course, the whole discussion is pretty much moot -- it's much
       easier to use say_xy() (below) instead of say(..., 1000) anyway.
   (7) Finally, ManaUser reminds us that the sprite number of a text sprite can
       be used with sp_kill() either to kill the text sooner or to make it stay
       longer or indefinitely.

    (back to Seth for a moment...) ]

  If you specify ` (reverse apostrophe/unshifted tilde) and then a [digit or
  other color character], it will show the text in this color.  Example:

    say("`4Hello!", 1); // (reddish) [not on my monitor]

  These are the same as LORD color codes in the BBS world. 1234567890!@#$%
  can be used.  (15 colors) [Ok, for those of us who never, ever heard of
  LORD, let alone memorized its color codes, it looks like they are:
  
    `1 = light magenta   `6 = light orange   `! = yellow (default*)
    `2 = light green     `7 = light gray     `@ = yellow (default*)
    `3 = bold cyan       `8 = dark gray      `# = hot pink
    `4 = orange          `9 = sky blue       `$ = yellow (default*)
    `5 = magenta         `0 = bold green     `% = white

  (*) So: why do we need *three* codes to give us the color we'd get by not
      specifying a color in the first place?  And `4 is not the code for red.
      There is no code for red, an annoying omission.]

  [ManaUser: "Actually, those aren't exactly the colors LORD used anyway..."]
   
  NOTE: If text doesn't seem to be showing up after a `%, it's probably because
        you have a I, D or J next, which are C syntax for other things - just
        put a space after `% and you'll be fine. [So why would this matter in
        DinkC?  Very curious that the standard C printf() syntax would be
        supported only sufficiently to get in the way...]

  [BUG ALERT: a very curious bug has been observed in say() and all its
  variations discussed below.  If the quoted string contains a colon, either
  at the beginning of the string or at least not preceded by a space, but then
  followed by a space, then a single non-space and another space, the say()
  will not function.  Example:
     say("bonus: 5 points", 1);   // DOES NOT WORK
  These, however, don't follow that exact pattern and so work fine:
     say("bonus : 5 points", 1);  // ok - space before colon
     say("bonus:5 points", 1);    // ok - no space after colon
     say("bonus: 10 points", 1);  // ok - no space after the
     say("bonus: 5", 1);          //        colon-space-nonspace combo
  The bug also applies if the colon is followed by two or more spaces:
     say("bonus:  10 points", 1);   // DOES NOT WORK

  (end of say() notes) ]

  See also: say_stop(), say_stop_npc(), say_stop_xy(), say_xy();
            busy(), initfont(), sp_kill(); create_sprite()


#say_stop()
# Category: Sprite, Text Sprite
# Prototype:
#   int say_stop( char string[100], int sprite );

  Same as [say()] but stops this script until the thing is said.

  [Prototype modified to note that it, too, returns the sprite number; though
  this is a pretty much irrelevant action since the script can't do anything
  until the text sprite is gone again.]

  [If Dink is frozen -- that is, if a freeze(1) command is in effect -- the
  command can be terminated prematurely by pressing the space bar.  Otherwise,
  it remains on the screen 2700 milliseconds for strings up to 35 characters
  long, with an increasing amount of time for longer strings -- 7700
  milliseconds for the maximum-length string of 100 characters.]

  See also: say(), say_stop_npc(), say_stop_xy(); busy(), initfont()


#say_stop_npc()
# Category: Sprite, Text Sprite
# Prototype:
#   int say_stop_npc( char string[100], int sprite );

  Same as say_stop, but will not let the PLAYER skip things by hitting space
  - if the player may be talking to someone else while this is called, (like
  two girls talking in the background) you should use this...

  [Seth's explanation up to this point makes perfect sense.  But then he goes
  on to say:]

  ...it is safe.  Otherwise callbacks may conflict.

  [This part is totally enigmatic.  What does he mean by "callback" in this
  context?  What would be "unsafe" about two girls talking in the background?
  Oh, never mind.  Maybe I just answered my own question...]

  [Kidding, ladies, I'm kidding.  Sorry 'bout that.]

  See also: say(), say_stop(), say_stop_xy(), initfont()


#say_stop_xy()
# Category: Text Sprite
# Prototype:
#   int say_stop_xy( char string[100], int x, int y );

  Same as [say_stop()] except automatically attaches it to 1000 and lets you
  specify X Y coordinates.

  See also: say(), say_stop(), say_stop_npc(), say_xy(), initfont()


#say_xy()
# Category: Text Sprite
# Prototype:
#   int say_xy( char string[100], int x, int y );

  Same as [say()] except automatically attaches it to 1000 and lets you
  specify X Y coordinates. (returns sprite # [of created text sprite]).

  [These two seem to be equivalent in all respects:
      int &ts = say_xy("any text", <x>, <y>);
  and...
      int &ts = say("any text", 1000);
      sp_x(&ts, <x>);
      sp_y(&ts, <y>);
  (end of say_xy() note) ]

  See also: say(), say_stop_xy(), sp_kill(), initfont()


#screenlock()
# Category: Enemy Sprite
# Prototype:
#   void screenlock( bool 1_or_0 );

  Pass this 1 to 'lock the screen'.  This means Dink cannot walk off the
  screen.

  Pass 0 to change it back to normal.

  [While the argument is defined as needing to be 0 or 1, the value -1 has
  an interesting effect: it locks the screen without saying so -- the screen's
  sidebars are not changed.

  Both ManaUser and Tyrsis advise of this bug with screenlock: While the
  screen is locked, Dink can walk all the way around the edges of the screen,
  regardless of hardness.  All that is necessary is to try to make him walk
  diagonally, forcing him against the edge.

  To demonstrate it, create these scripts in the the STORY directory of your
  favorite dmod:
     KEY-76.C:
        void main ( void )
        {
           screenlock(1);
           kill_this_task();
        }
     KEY-85.C:
        void main ( void )
        {
           screenlock(0);
           kill_this_task();
        }

  Now you can press the L key to lock the screen or the U key to unlock it.
  Play the dmod and find a screen with hardness at the edge that Dink could
  not ordinarily walk across.  If that hardness blocks him from walking, say,
  up to the next screen from the right side, do this: (1) press the L key to
  lock the screen; (2) move Dink to the right side, then (3) continue holding
  the right arrow while you now push the up arrow.  Dink will walk right
  across the hardness unimpeded.  Now press the U key to unlock the screen,
  and you could very well find Dink in another part of the game altogether,
  perhaps on water or something.

  (In the time since I was originally informed of this bug, I have discovered
  it is even worse than I thought: it also applies to monsters!  In a screen
  with a lot of enemies, such as dmod authors like to do with slimers, it is
  possible for one of them to escape along the edge of screenlock.  Without
  resorting to a cheat, the player must be careful in this case to leave at
  least one monster still alive in the normal area while using the screenlock
  bug to hunt the escapee.  If the escapee is the last monster killed, the
  screenlock will be removed while Dink is outside the normal area, probably
  leaving him with no legitimate way to return.)

  What to do about this bug?  Outside of nagging Seth about it, the options
  are not appealing.  It is not always practical to design your dmod such that
  screen-locked battles take place only on screens with no edge hardness. Both
  Tyrsis and ManaUser suggest the use of invisible sprites whose hardness and
  the actions of their touch() procedures can be activated and deactivated
  along with the screenlock; I have also had good success with a constantly-
  running loop that monitors Dink's position.  These techniques can be
  complicated to implement and debug, however, and the looping technique
  in particular is not going to block monsters.]


#script_attach()
# Categories: Script Management; Sprite
# Prototype:
#   void script_attach( int #_to_attach_it_to );

  You can attach the current script to another sprite (it will be unattached
  from the current one), or attach it to 1000, which means "no sprite, will not
  die on its own".  For instance, if you want Dink to say "I'm poisoned" every
  5 seconds for infinity, and you don't want the script deleted when Dink moves
  to a new screen, setting it 1000 is the way to go.  [The obvious approach,
  attaching a script like this to Dink (sprite 1), is not supported.]  You must
  then use kill_this_task() to get rid of it; if you don't, it will be there
  FOREVER. [Actually, only until the player loads a saved game.]

  [Notes:
   (1) To attach a *named* script to a sprite, use sp_script().  See also note
       (4), below.
   (2) Watch out for pitfalls when using this command: it's common to use it
       in a die() procedures, for example, because those procedures are only
       allowed to run a short time before the Dink engine kills the script
       because its attached sprite is dead.  Just don't forget that the variable
       &current_sprite IMMEDIATELY CHANGES TO 1000 after a script_attach(1000),
       so make sure the "&save_x = sp_x(&current_sprite)" and other lines that
       need to reference &current_sprite are run first.
   (3) ManaUser reports that script_attach(0) behaves similarly to
       script_attach(1000), except that a script attached to sprite 0 dies
       automatically "when Dink leaves the screen (sometimes handy)".
   (4) In the case of attaching scripts to actual sprites, this command is
       no substitute for sp_script() (see).  Effectively, only the currently-
       running procedure is attached, not the entire script.  Even if this
       script includes, say, a talk() procedure, it will not get run if Dink
       subsequently talks to the sprite.
          ManaUser: "Odd it doesn't seem to replace the sprite's old script
       either.  I wonder in what sense then, it is 'attached'?"  Good question.
       As he points out, if this command references a sprite with its own
       script, that script does not seem to be affected in any way.  Looking
       at all code released by RTSoft, the only examples I see of this
       command are script_attach(1000).  That and ManaUser's script_attach(0)
       are probably its only useful forms.]

   See also: is_script_attached(), kill_this_task(), sp_script()


#scripts_used()
# Category: Script Management
# Prototype:
#   int scripts_used( void );

  Returns the # of scripts being used right now.  If > 190 or so, you probably
  don't want to create any more until the guy moves to another screen or
  something.

  [This function seems to work as advertised, and can be used to show that
  "...scripts being used" means more than might be expected at first.  This
  script can be most instructive:

     void main( void )
     {
        int &active_scr = scripts_used();
        say("scripts in use: &active_scr", 1);
        kill_this_task();
     }

  Name this script key-45.c, for example, and any time you press the Insert
  key, Dink will tell the number of active scripts.

  In a well-behaved, well-debugged dmod, the count will be a total of the
  following:
    1 if a script is attached to the screen Dink is currently on (in DinkEdit
        terms, if this "map" has a "base script");
    1 for Dink's currently-armed weapon;
    1 for Dink's currently-armed magic spell, if he knows magic;
    1 for every sprite on the screen with an attached script, regardless of
        whether the same or different scripts are attached.  (For example, in
        a screen with four pillbugs, the count will include 4 for the pillbugs,
        even though en-pill.c might be attached to each one.  Every sprite gets
        its own separate "instance" of an attached script.)
    1 for any script that has done a "script_attach(1000)" and not done a
        kill_this_task(), such as the "I am poisoned" script Seth exemplifies
        for script_attach();
    1 for any script that has done a "script_attach(0)" while Dink was on
        the current screen; and
    1 for any active key-## script.

  So in a screen where there is nothing going on, the script above will
  typically say 2 or 3 scripts are active: 1 for the key-## script itself,
  1 for Dink's weapon, and often, 1 one for Dink's magic.

  In a less ideal environment, scripts_used() will reveal the presence of
  scripts that have failed to execute kill_this_task() when appropriate.

  And a note about Seth's "If > 190 or so" condition: in any dmod with
  realistic scripts, long before you get 190 scripts started, you are going
  to run out of the 248 available slots for variables.  See the notes in the
  "Variables" category header for more information. ]

  See also: kill_this_task(), script_attach(), spawn()


#set_button()
# Category: Undocumented
# Prototype:
#   void set_button( int button, int function );

  [This undocumented command appears once in Escape.c as released by RTSoft,
  in a routine to change the default function of the various DinkC buttons.
  The routine supports values of 1-10 for "button"; these apparently are the
  supported values for "function"

     Function#  Function Name  Default Key Assignment
     ---------  -------------  ----------------------
         1      Attack         button 1, Ctrl
         2      Talk/look      button 2, Space bar
         3      Magic          button 3, Shift
         4      Item screen    button 4, Enter
         5      Main menu      button 5, Escape
         6      View map       button 6, "M" key

  Notes:
   (1) The choice() construct supports a pseudo-variable "&buttoninfo" that
       will, in a choice() menu, show the function assigned to each button
       -- see "Part 5: How the Choice Statement Works" for more details.
   (2) The "M" key is regarded as "button 6" during normal play but not by
       wait_for_button() -- see wait_for_button() for more information.
   (3) It is very unlikely anyone really uses the set_button() routine in
       escape.c -- due to a bug, it will not work in "Prophecy of the
       Ancients", for example, yet no one has apparently ever complained
       about it. ]


#set_callback_random()
# Categories: Script Management; Enemy Sprite
# Prototype:
#   void set_callback_random( char name_of_proc[20], int min, int max );

  Tells [the dink engine] to run this proc inside of the current script every
  "min + random (max)" thousandths of a second.  [Lest there be any doubt,
  "min" is short for "minimum", not "minutes" -- both numbers are 1000ths of
  a second.  Example, from a dragon script's main():

     set_callback_random("target",500,2000);

  Calls procedure target(), in the same script, randomly every 500 to most
  likely 2499 milliseconds.

  ManaUser: "I advise using set_callback_random() as little as possible.
  It isn't one of the more trustworthy commands.  It does nasty things like
  failing to call back if it gets distracted and raising the dead."
     I've seen that bug in a dmod, but the author fixed it when I reported
  it to him.  I wonder if that's what it was?
     In any case, what is probably missing is a documented way of turning
  this command off, such as in the die() procedure, when the callback is no
  longer appropriate.  ManaUser suggests:
        sp_script(&current_sprite, "");
     This simply kills the script attached to the sprite, appropriate if the
  die() procedure consists of a few, quick commands.  If the sprite's death
  needs to begin a conversation or a cut-scene, my suggestion would be to put
  all that in a separate script, spawn() it, and then use the null sp_script()
  command ManaUser suggests. ]

  See also: "my_proc()"


#set_dink_speed()
# Category: Dink Specific
# Prototype:
#   void set_dink_speed( int speed );

  Lets you change Dink's walking speed.  3 is normal, 2 is fast. 1 is...
  I don't know, never tried it, but I'm gonna guess real fast. [Whatever.
  (1) Speed 1 is The Ultimate Cheat's "super fast" speed.  And it is most
      certainly "real fast."
  (2) "Speed" is used as a divisor, so Dink can be dogged slower than normal
      by using a speed of 4 or higher.
  (3) As might be expected if "speed" is used as a divisor, the game crashes
      if speed is 0.
  (4) The function indeed does not give a useful return value.  A speed of
      of -1 has the interesting effect of locking Dink in place.  He isn't
      frozen -- all the keys still work and Dink responds to the arrow keys
      by turning in place -- he just can't move from the spot he's standing
      on, like he's standing in hardness.
  (5) Changing Dink's speed does not change his animation speed. The Ultimate
      Cheat sets sp_frame_delay() as a visual cue: 45 for speed 2, 22 for
      speed 1, and 0 (whatever 0 means in this case) for speed 3.
  (6) This function definitely has an effect on the value returned by
      sp_speed(1, -1).  Unfortunately, that effect is neither consistent nor
      predictable.  It varies from machine to machine and, occasionally, from
      moment to moment.  Apparently, the dink engine dynamically determines a
      suitable sp_speed() setting when set_dink_speed() is invoked.  Here is
      how to witness that effect:

         void main( void )
         {
            set_dink_speed(2);
            wait(1); // (effect on sp_speed() is not instantaneous)
            int &spd = sp_speed(1, -1);
            say("My speed is now &spd", 1);
            kill_this_task();
         }

      Call this script, say, key-45.c to attach it to the Insert key.  Press
      the attached key to make Dink tell his sp_speed() setting.  Now press
      and hold the key.  If your experience is like mine, what Dink says will
      change occasionally.  Change the setting in the set_dink_speed() line
      to see the effect of other settings.
  (7) Speeds faster than 3 should be used advisedly.  Game crashes have been
      been reported at speed 1, the speed Seth says he never tried; and even
      at speed 2.  Additionally, both speeds can be hard to control -- Dink
      can suddenly stop responding to keys and just run off the screen if it
      is not locked.

  (end of set_dink_speed() notes) ]

  See also: sp_speed()


#set_keep_mouse()
# Category: Mouse
# Prototype:
#   void set_keep_mouse( bool O_or_1 );

  Let's say you wanted cursor-like mouse control in your addon, set this right
  after loading the game, and the mouse support will stay.  Note that sprite 1
  [Dink or whatever character the player controls] must have brain 13 [mouse
  brain as opposed to the usual brain 1 for a human -- see description for
  sp_brain()] for it to activate. (1 is yes).

  See also: set_mode(), sp_brain()


#set_mode()
# Category: Mouse
# Prototype:
#   int set_mode( int mode );

  Sets the mode.  You should never use this, is used for low-level stuff.
  Mode 2: This loads the player's screen and draws the statbar stuff then
          changes it to mode 3 automatically, which is 'regular game mode'.
          [I assume this is mode "1", not "2" ???]
  Mode 2: In mouse selection mode (use keep_mouse [set_keep_mouse?] if you
          want mouse support INSIDE the game portion as well).
  Mode 3: Game is running now.

  See also: set_keep_mouse()


#show_bmp()
# Category: Screen Drawing
# Prototype:
#   void show_bmp( char string[200], bool over_position?, int unused );

  ["string" is a filename and directory, relative to the DMod's directory.]
  Example:

     show_bmp("tiles\crap.bmp", 0,0);

  The first # parm [<over_position?>] tells [the dink engine] to show a cross
  hair of their position on the map, used in Dink to show the location, only
  works if you make the map from a screen shot from DinkEdit.

  [Notes:
    1) show_bmp() pauses the game and shows the bitmap until the user presses
       one of dink's "button" keys: Ctrl, Space, Shift, Enter, Esc, or the M
       key.
    2) Tyrsis warns, "problems with DirectX may arise if your bmp size is not
       exactly 640x480.  The game may even crash, but not with all videocards".
       I have seen these problems: for example, strange flashing when Dink is
       moved to a new screen.  What I have not seen is the display of the
       bitmap itself.  In my experience, if you attempt to display a bitmap
       that is not 640x480, all you get are DirectX problems that persist
       until you get out of the game and go back in...
    3) The bitmap apparently does not need to use the "dink palette" unless
       it is a game map, where the "over_position?" argument is 1, telling
       the dink engine to display a spark that corresponds to Dink's current
       position in the 32x24-screen dink world.  In that case, Tyrsis warns
       that if the "dink palette" is not present, the spark's color is
       unpredictable.
          Tyrsis: "If you try to show several BMPs one after another (e.g.,
       2 pages of a letter), the second one may look strange without dink
       palette.  I had this problem."

  When "over_position?" is 1, the dink engine treats the bitmap as a 32x24
  grid, and displays a spark in the imaginary cell that corresponding to 
  Dink's current screen, subject to the following rules and bugs: 
    a) The author may, in DinkEdit, define any screens as "inside", as in
       "inside a building" or "inside a cave".  The spark will always be
       shown in the last screen Dink visited that was NOT "inside".
    b) If Dink was teleported or "warped" to a screen, the spark location
       is not updated.  It is only updated during normal screen scroll.
       The spark location is *not* a representation of &player_map.
    c) The spark is never shown if it should be shown in the right-most
       edge of the map, the 32nd screen of any row. ]

  See also: copy_bmp_to_screen()


#sound_set_kill()
# Category: Sound
# Prototype:
#   void sound_set_kill( int soundbank );

  Good way to kill a certain sound. (Any sound, including [one set to]
  "survive").

  [<soundbank> is the remembered return value from playsound() (see).]

  See also: sound_set_survive(), kill_all_sounds()


#sound_set_survive()
# Category: Sound
# Prototype:
#   void sound_set_survive( int soundbank, bool 1_or_0 );

  If 1, sound will SURVIVE a load_map() [load_screen()?] command.  Use this
  on an already looping sound to provide 'looping music'.

  [<soundbank> is the remembered return value from playsound() (see).]

  See also: sound_set_kill(), kill_all_sounds()


#sound_set_vol()
# Category: Sound
# Prototype:
#   void sound_set_vol( int soundbank, int vol );

  Change the volume of a sound.  0 means NO CHANGE,  -10000 means VERY low
  -- remember, it doesn't turn off the sound (even if you can't hear it), it
  just makes it play quieter.  No amplification is done [meaning that positive
  "vol" values are not supported and treated as 0, maximum volume].  -1500
  sounds about 'half volume'.

  [Seems to me that -1000 is about half volume, you'll have to decide for
  yourself.  <soundbank> is the return value from playsound().  This key-##
  script can be used to experiment:

    void main( void )
    {
      int &sound = 8;
      int &vol = -500;
      playsound(&sound, 11000, 0, 0, 0);
      wait(500);
      int &bank = playsound(&sound, 11000, 0, 0, 0);
      sound_set_vol(&bank, &vol);
    }

  Call this key-45.c, for example, and this routine will play a sound twice
  when you press the Insert key.  The first sound will be full volume, the
  second will be under the control of the sound_set_vol().]

  See also: playsound(), sound_set_survive(), sound_set_kill()


#sp()
# Categories: Editor Data; Sprite
# Prototype:
#   int sp( int editor# );

  VERY important - this returns the actual SPRITE # of a certain EDITOR
  screen object.  For instance, if you placed one tree on a screen with the
  editor, you could use [sp(1)] to get the sprite # of the tree.  (It would
  most likely be 2: Dink is ALWAYS 1; but never guess.)

  [The prototype was originally shown as "int sp(1)"; the one now given is my
  own.
     This function and sp_editor_num() are used to translate between object
  numbers assigned by DinkEdit.exe and sprite numbers assigned by Dink.exe
  when the game is running.  Why aren't they the same?  The main reason seems
  to be that the dink engine conserves sprite numbers by not assigning them
  to DinkEdit objects whose type is zero(0), Ornamental.  By extension, sprites
  whose editor_type() was set non-zero on a previous visit to this screen are
  also not assigned sprite numbers.
     DinkEdit object numbers are not always easy to discern.  The (I) key can
  be used to pop up a brief box of information on each sprite on a map; but on
  a busy screen it is not always possible to tell which box goes with which
  sprite; and the object number can be covered up or off-screen.  The surest
  way to know is to press Tab to go into sprite edit mode, then use the "]" or
  "[" keys to move from sprite to spite until the green box selects the one
  you want.  Now the editor's number will be the one listed as "last sprite
  touched" in the text at the bottom of the screen.]

  See also: sp_editor_num()


#sp_active()
# Category: Sprite Attribute (DinkEdit: none)
# Prototype:
#   int sp_active( int sprite, int value <-1 to not change> );

  1 if sprite is active, 0 if not active.  You can kill a sprite using this,
  or check to see if one is active.

  [It is not 100% clear what "active" means in this context.  This is what I
  found out trying to do a script-only fix on a warp that had both the warp
  property and a warping touch() script, and I wanted to temporarily disable
  the warp property (which seems to have no sp_xxx() functions associated with
  it): in the warp's main() procedure, I did this:
      sp_active(&current_sprite, 0);
      wait(2000);
      sp_active(&current_sprite, 1);
  The result was that main() quit after the first statement; the sprite was
  not drawn; but its warp property remained active!  Not what I wanted, but
  surely a useful result in some other context.

  Here is a more useful example of sp_active() in action.  If a sprite needs
  to be effectively removed from a screen under certain conditions, and it
  is in vision 0 (meaning you can't change visions to get rid of it), this
  is what it can do in its main() procedure:
      
      if (<spite-should-be-disabled>)
      {
        sp_hard(&current_sprite, 1);       // make sprite *not* hard
        draw_hard_sprite(&current_sprite); // re-draw sprite's hardness
        sp_active(&current_sprite, 0);     // make sprite not active
        return;                            // probably never executed!
      }

    (note from Tyrsis: "If I want to enable/disable the warp property
  assigned by Dinkedit, I just play with its hardness. Hard = warp, not
  hard = no warp.  Good for switching it by another sprite's script.") ]

  See also:
    sp_disabled()    [for comparison -- they are not just different ways
                        of doing the same thing.]
    sp_nodraw()      [to make sprite invisible only]
    create_sprite()  [this is the fastest way to kill a created sprite]


#sp_attack_hit_sound()
# Category: Weapon
# Prototype:
#   void sp_attack_hit_sound( int sprite, int value );

  Sets the sound effect # to play when this sprite attacks and hits something.
  If 0, defaults to 9, punch. (used in item-sw1.c)

  [It's used similarly in all three swords.  Example from item-sw1:

     void arm( void )
     {
       ...
       sp_attack_hit_sound(1, 10);
       sp_attack_hit_sound_speed(1, 8000);
       ...
     }

     void disarm( void )
     {
       sp_attack_hit_sound(1, 0);
       ...
     }

  Remember, this sound is only played if the weapon hits something.  To make
  a sound when the weapon is wielded, item-sw1 does this:

    void use( void )
    {
      ...
      playsound(8, 8000,0,0,0);
    }

  It's also used in item-axe.  Since the axe is really a missile, the sound
  has to be attached to it rather than Dink.  Here's some lines from item-axe:

    void use( void )
    {
      ...
      &junk = create_sprite(&mholdx, &mholdy, 11, 85, 1);
      sp_seq(&junk, 85);
      ...
      sp_timing(&junk, 0);
      sp_speed(&junk, 6);
      sp_strength(&junk, 15);
      ...
      sp_range(&junk, 10);
      sp_flying(&junk, 1);
      sp_attack_hit_sound(&junk, 48);
      sp_attack_hit_sound_speed(&junk, 13000);
      &mshadow = create_sprite(&mholdx, &mholdy, 15, 432, 3);
      ...
    }

  ManaUser: "You can use it on enemies too."
     In this case the <sprite> argument would most likely be &current_sprite,
  though I see no examples of this command applied to an enemy sprite. ]

  See also: playsound(); sp_attack_hit_sound_speed() [below]


#sp_attack_hit_sound_speed()
# Category: Weapon
# Prototype:
#   void sp_attack_hit_sound_speed( int sprite, int value );

  Sample speed to play the above sound.  Usually at 22050.  Forget to set this
  and the sound won't play...

  See also: sp_attack_hit_sound(), above, for more info and example.


#sp_attack_wait()
# Category: Enemy Sprite
# Prototype:
#   void sp_attack_wait( int sprite, int value );

  Time in 1000ths of a seconds that you would like this monster to forget
  about its target.

  [Here is a fairly typical example from en-slay.c as released by RTSoft:

     void attack( void )
     {
       playsound(27, 22050,0,&current_sprite, 0);
       &mcounter = random(4000,0);
       sp_attack_wait(&current_sprite, &mcounter);
     }

  This apparently will make the enemy sprite not attack for at least 0 to 3.999
  seconds.  "At least," I say, because the monster also has to be within the
  specified sp_distance() from its target before it will attack.

  Since attack() is not referenced in this script, I gather that it is one
  of the many procedures (like touch(), talk(), etc.) that are referenced
  automatically by the dink engine: in this case when it is decided that the
  monster is to attack its designated sp_target().

  ManaUser: "...take a look at the return value of this command (yes it has
  one), it seems to be a timer in milliseconds but I can't tell what it's
  timing."
     It is true that the command actually does return a curious value.  As
  near as I can tell, it is the computer's "up time"; that is, the time since
  it was last powered up or re-booted, measured in milliseconds:

     int &uptime = sp_attack_wait(&current_sprite, -1);

  The "-1" is NOT interpreted as a request for a return value, however: it
  makes the enemy sprite want to attack immediately. ]

  See also:
    sp_callback_random() [another command used to control monster timing]
    sp_target()          [tells a monster to attack, and which sprite]


#sp_base_attack()
# Category: Animation, Enemy Sprite, Sprite Attribute (DinkEdit: Alt 2)
# Prototype:
#   int sp_base_attack( int sprite, int value );

  Like [sp_base_walk(), below,] but determines the sequences to use to attack.

  [Notes:
  (1) According to sp_target() descriptions from both Seth and ManaUser, this
      command must be used (or its counterpart DinkEdit attribute set) in order
      for an enemy sprite to attack.
  (2) IMPORTANT: the enemy still will not be able to truly attack -- that is,
      inflict more than touch-damage -- unless a frame in every "true" sequence
      derived from the base sequence is defined as the "hit frame" using a
      "set_frame_special" command in dink.ini.  In the original game, for
      example, purple boncas cannot hit.  Their sp_base_attack is 620, but
      these commands are missing from dink.ini:
         set_frame_special 622 5 1
         set_frame_special 624 5 1
         set_frame_special 626 5 1
         set_frame_special 628 5 1
  (3) The return value is always the same as the second argument.  Passing -1
      as the second argument turns off an enemy sprite's ability to attack.]

  See also: sp_base_walk(), sp_frame_delay(), sp_dir(); sp_target();
            Part 2A, "Commands To Get/Change Sprite Attributes";
            Part 2B, "About PSEQ and PFRAME"


#sp_base_death()
# Categories: Animation; Enemy Sprite; Sprite Attribute (DinkEdit: Shift 6);
#             Undocumented
# Prototype:
#   int sp_base_death( int sprite, int value );

  [As noted, corresponds to the DinkEdit attribute selected with Shift+6 key.
  It is not known which brains look for this value: brain 9 (typical monster
  brain) does for sure; others need to be tested.

  Although the prototype shows it returns a value, the return is the same
  as <value> argument.  A <value> of -1 returns -1, not the sprite's current
  setting.

  As with all "sp_base" functions, "value" is normally a sequence number
  ending in 0 to which the sprite's current sp_dir() direction will be added
  to get the sequence to display upon the sprite's death.  For example:

        sp_base_death( &current_sprite, 550 );

  If the sprite is moving in direction 1 when it dies, the 1 is added to the
  550 and the sprite's displayed sequence becomes 551.  What happens if the
  sprite is moving in direction 2 when it is killed and only sequences 551,
  553, 557, and 559 are present?  In this case the dink engine seems use some
  algorithm to choose among the available sequences.  It is common for enemy
  sprites to only have actual sequences corresponding to "base_death + 1" and
  "base_death + 3", yet one of those two will always be displayed when the
  sprite dies.

  Additional processing: after some discussion, I think there is agreement
  between ManaUser and ReDink1 on this:
    - The default for "value" is -1.
    - If "value" is either -1 or 0, the dink engine uses "sp_base_walk() + 5"
      as the death sequence.  This is common for most "human" sprites, such
      as knights or merchants, for example.
    - if there is no sequence "sp_base_walk() + 5", the dink engine uses
      sequence 164, an exploding blood ring, as the death sequence.  Pigs
      are a common example of this.

  Sylvain Beucler notes that this command has no effect on Dink.  To change
  Dink's death animation, you must change the sp_seq() command in the die()
  procedure of script dinfo.c. ]

  See also: sp_base_walk(), sp_base_attack(), sp_base_idle()


#sp_base_idle()
# Categories: Animation; Sprite Attribute (DinkEdit: unshifted 7); Undocumented
# Prototype:
#   int sp_base_attack( int sprite, int value );

  [As noted, corresponds to the DinkEdit attribute selected with the 7 key.
  It is the same concept as sp_base_walk() except it is used to select the
  sequence to play when the sprite is standing still.  It is not yet known
  which brains look for this value: brain 1 (Dink) does for sure; brains 3
  (duck), 4 (pig), and/or 16 ("smart people") might; the others are unlikely.

  Although the prototype shows it returns a value, the return is the same
  as <value> argument.  A <value> of -1 does not cause the sprite's current
  setting to be returned.]

  See also: sp_base_walk(), sp_base_attack(), sp_base_death()


#sp_base_walk()
# Category: Animation, Sprite Attribute (DinkEdit: unshifted 6)
# Prototype:
#   int sp_base_walk( int sprite, int value );

  Should be 10, 20, 30 etc.  This means the sprite will play animations from
  that base -- if they walk left (direction 4, look at your numpad) it would
  play seq 104 if the base was 100.  All bases work this way.  If diagonals
  don't exist, it will use non-diagonals for diagonal movement automatically,
  and vice versa.

  [In other words, "value" is a not-necessarily-existent "seq", ending in zero
  for convenience, to which the sprite's current sp_dir() attribute is added
  to get the "seq" to play to show the sprite's movement in that direction.

  What about value+0 and value+5, which are not sp_dir() directions?
  ManaUser: "The value+5 is the sprite's death frame if you don't specify
  another one.  I don't think the value itself is (used for) anything at all."

  Seth's "-1 to not change" notation has been removed from the prototype:
  passing -1 seems to cause the sprite to only use a single walking sequence
  no matter which way it goes.  The return value is always the same as the
  second argument.  ManaUser also sent me this note about it: "'-1 not to
  change' doesn't work with with sp_base_walk().  It will actually set
  sp_base_walk to -1.  A possible workaround is to check their seq instead,
  divide it by 10 then multiply it by 10 (i.e. round down)." ]

  See also: sp_base_attack(), sp_frame_delay(), sp_dir();
            Part 2A, "Commands To Get/Change Sprite Attributes";
            Part 2B, "About PSEQ and PFRAME"


#sp_brain()
# Categories: Animation; Enemy Sprite; Mouse;, Dink Specific;
#             Sprite Attribute (DinkEdit: unshifted 3); Text Sprite; Weapon
# Prototype:
#   int sp_brain( int sprite, int value <-1 to not change> );

  Lets you set the brain of a sprite.  This tells the sprite how to behave,
  very important!

  [brain 0: no brain -- sprite will not do anything automatically]
   brain 1: Human [that is, sprite 1 / Dink] brain.
   brain 2: ["Dumb Sprite Bouncer", per DinkEdit.  See below.]
   brain 3: Duck brain.
   brain 4: Pig brain.
   brain 5: When seq is done, kills but leaves last frame drawn to the
            background
   brain 6: Repeat brain - does the active SEQ over and over.
   brain 7: Same as brain 5 but does not draw last frame to the background.
  [brain 8: text sprite brain]
   brain 9: Person/monster ([Only] diagonals)
   brain 10: Person/monster ([No] diagonals)
   brain 11: Missile brain - repeats SEQ [compare brain 17].
   brain 12: Will shrink/grow to match size in sp_brain_parm(), then die
               [WITHOUT giving any experience...]
   brain 13: Mouse brain (for intro, the pointer)
               (do a set_keep_mouse() to use inside game as well)
   brain 14: Button brain.  (intro buttons, can be used in game as well)
   brain 15: Shadow brain.  Shadows for fireballs, etc.
   brain 16: Smart People brain.  They can walk around, stop and look.
   brain 17: Missile brain, [like 11] but kills itself when SEQ is done.

  [Info on brain 2:

    ManaUser: "Take item-fb.c and change where it says brain 11 to 2.  It's
    fun, try it.!"  Thanks, it is fun -- and instructive. But sp_brain() is
    not used in this script: "11" is the <brain> parm of the create_sprite()
    commands that create the fireballs.  Change them all (or at least the four
    for &mydir == 2, 4, 6, and 8), then find Dink a nice open screen with no
    monsters and start shooting fireballs.  It's a trip!
       Unfortunately, I am still looking for a practical application for this
    brain.  I was hoping it could be used to animate a "marching guard" that
    would simply walk back and forth in its sp_dir() direction and the opposite
    direction.  Worked only to a degree: the sprite seemed to ignore hardness
    in its march, and -- possibly worse -- the sprite's walking animation was
    played only for a second, probably a single run through the animation.
    After that, he just seemed to "float" back and forth along his beat.

       P.S.: credit also to ManaUser for notes on brains 8, 9, and 10.  In the
    case of the latter two, for confirming that the descriptions are reversed
    in the original document.]

  [Info on brains 5, 6, 7 (animations -- probably also applies to brains
    11 & 17, missiles, below):

    Use sp_frame_delay() to control the speed of the animation.  Use
    sp_reverse() to play the animation backwards.  Brain 6 only: use
    sp_timing() to introduce a pause between animation cycles.]

  Info on brain 11 [and 17 -- missile brains]:

    Check item-fb.c for an example of the missile brain used. [sp_brain_parm()]
    and [sp_brain_parm2()] can be used to set certain sprites this missile
    cannot damage. (for instance, the guy who shot it and the missile's shadow
    effect, if used).

    [It is not intuitively obvious what all sprites are considered "missiles".
    Four distinct categories come to mind: (1) magic attacks such as the
    fireball and acid rain spells; (2) thrown weapons such as the flying axe
    and, in some dmods, the boomerang; (3) arrows launched by bows; and
    (4) "static" (non-moving) missiles, most notably the bomb.  The shared
    characteristic of all these is that they act as Dink's agents: if the
    damage they inflict on a monster kills it, Dink gets the experience.]

  [Info on brain 14 (button brain):

    ManaUser: "Brain 14 'can be used in game as well.'  I never really took
    notice of that before, but I think it may have some real potential in-game.
    Brain 14 can call three special procedures: buttonon(), buttonoff(), and
    click().  Click() only works when sprite one is a mouse pointer, but the
    other two work with Dink as well.  buttonon() is a lot like touch(), but
    it's called only once each time Dink touches it.  It will only be called
    again only if he moves away and back again.  Buttonoff() is called when
    when he breaks contact with it.  This could be even more useful since there
    is no 'untouch' procedure."
       Hmmm.  Sounds like using buttonon() instead of touch() could do away
    with all that nonsense of toggling sp_touch_damage() between -1 and 0...]

  Info on brain 15 (shadow brain):

    To use this brain, use sp_brain_parm() to set a sprite to mimic.  It will
    copy this sprite's location until the sprite is killed, at which point it
    will kill itself.  [See also kill_shadow() and sp_follow().]

  [Perhaps other brains need info, too.  Additionally, it needs to be noted
  that there is more at stake in the selection of "brain" than just behavior.
  For example, in a dmod I was tinkering with, a duck that looked like a duck
  in DinkEdit, and was obviously supposed to be a duck, had a brain of 9
  instead of 3 for no certain reason.  As a result, during gameplay, the
  sprite was shown as a girl!  Adding an "sp_brain(&current_sprite, 3)"
  statement to the main() procedure of the sprite's attached script was
  *all* that was necessary to make it look like a duck again.]

  See also: sp_brain_parm/parm2() [below]; most of the functions in any of
     of the categories where sp_brain() is listed as a member.


#sp_brain_parm()
#sp_brain_parm2()
# Categories: same as sp_brain()
# Prototype:
#   int sp_brain_parm( int sprite, int value <-1 to not change> );
#   int sp_brain_parm2( int sprite, int value <-1 to not change> );

  Some brains use [these parms] for special stuff.  Like the shrink/grow brain.
  (used when they pick up an item).

  [Known so far:
    Brain 11 and 17 (missile brains):
      The <value> of of both sp_brain_parm() and sp_brain_parm2() should be
      the sprite numbers of sprites that will not be damaged by the missile.
      Almost invariably, one is the sprite number of the missile's "shadow"
      and the other is 1, Dink's sprite number.
    Brain 12 (shrink/grow brain):
      The <value> of sp_brain_parm() is a percentage of the sprite's base size.
      Brain 12 will show the sprite quickly changing from its sp_size() to this
      size, then the sprite will be "killed".
    Brain 15 (shadow brain):
      The <value> of sp_brain_parm() is the sprite number of which this sprite
      is to be a "shadow".

    ReDink1 notes that for brains where these values are *not* used -- his
    examples are 0 and 9 -- sp_brain_parm() and sp_brain_parm2() can be used
    to store temporary values.  See sp_gold() for more discussion on this.]

  See also: sp_brain() [above]


#sp_defense()
# Category: Sprite Attribute (DinkEdit: Alt 3)
# Prototype:
#   int sp_defense( int sprite, int value <-1 to not change> );

  Defense of a sprite -- this is taken right off the top of the damage done
  to them.  Someone with a defense of 20000 would likely be invincible. <g>

  [Notes:
   (a) The documented ways to make a sprite invincible are setting sp_nohit()
       to one(1) or sp_hitpoints() to zero(0).  These methods, however, may not
       cause the sprite to act the way you want it to in combat.  Assigning
       outlandishly high values to sp_hitpoints() or, as Seth suggests,
       sp_defense() may fit your storyline better.  It is best to try the
       alternatives and see which you like.
   (b) There is no special meaning to setting sp_defense() to 0, it simply
       means the sprite has no defense.  ManaUser cites pillbugs as an
       example of such sprites.
   (c) ManaUser goes on to note that "negative defense even raises damage to
       a creature."  As stated in the prototype, however, defense may not be
       set to exactly -1 by this command, since that value has the special
       meaning of just returning the sprite's current defense without
       changing it.

  In combat, strength, defense, and hitpoints interact about like this:

    1) When Dink or a monster hits the other, a number of hitpoints is randomly
       selected from the upper half of its strength range.  In DinkC terms, you
       might write it like this:
           int &hit_work = sp_strength( <hitting-sprite>, -1);
           &hit_work / 2;
           int &hits = random( &hit_work, &hit_work );
           &hits += 1;

    2) Now the defense of the sprite that got hit is figured in:
           &hits -= sp_defense( <sprite-getting-hit>, -1 );
           if (&hits < 0)
               &hits = 0;

    3) Make sure the sprite cannot always get off scott-free (this algorithm is
       used in hurt() and when Dink gets hit; it does not seem to always apply
       to Dink hitting a monster, especially when he hits it with magic):
           if (&hits == 0)
               &hits = random( 2, 0 ); // random() returns 0 or 1

    4) Finally, apply the hit:
           int &health = sp_hitpoints( <sprite-getting-hit>, -1 );
           &health -= &hits;
           sp_hitpoints( <sprite-getting-hit>, &health );
           if (&health <= 0)
               die();        // note: Dink's die() is in script "dinfo"

    The mock code above is only given to help show the interrelationships
    between strength and defense in battle.  It must be noted that Dink's
    attributes are kept in variables: &strength instead of sp_strength();
    &defense instead of sp_defense; &life instead of sp_hitpoints().]

  See also:
    hurt(), sp_hitpoints(), sp_nohit(), sp_strength(), sp_touch_damage();
    Part 2A, "Commands To Get/Change Sprite Attributes"
    

#sp_dir()
# Category: Sprite Attribute (Dinkedit: none)
# Prototype:
#   int sp_dir( int sprite, int value <-1 to not change> );

  [Numeric keypad] direction of sprite.  If this is set, the sprite will face
  in this direction.  (based on what is in sp_base_walk [*]).  If a speed is
  set, it will be moving in this direction.

  [(*) I assume this means that sp_dir() always sets the sprite's direction,
  but based on what graphics are available for it as indexed off of
  sp_base_walk(), that direction may not be visually apparent.]

  See also: Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_disabled()
# Category: Sprite Attribute (Dinkedit: none)
# Prototype:
#   int sp_disabled( int sprite, int value <-1 to not change> );

  If 1, [sprite is disabled] -- it is there, but not being drawn or moved.  I
  used this on the bridge, to make a hardness I could remove quickly without
  [wanting] to deal with 'visions' in the editor.

  [From what I can tell, when a sprite disables itself in its own main(), it
  is just not drawn, similar to sp_nodraw().  Its scripts and warp properties
  remain active.  This is somewhat different from a sprite being sp_active(0),
  which also turns off its scripts.

  ManaUser: "I think it turns off the brain and maybe a few other things too."
  Thanks, ManaUser.  That clears it right up. ;-) Kidding, of course.  Turning
  off the brain means a disabled sprite would not move on its own, and that
  seems logical; as for your "other things", we'll be on the lookout.

  As for Seth comment about "not wanting to deal with 'visions' in the editor,"
  I think his cure was worse than the disease.  Script s1-brg is attached to
  to the screen that has the bridge between Dink's hometown and the castle.
  If he has not yet paid the 100gp to the hustler, it sets &vision to 1 (even
  though, as he said, he created no vision 1 sprites) and creates the guy on
  the bridge.  The guy gets script s1-brg2, which creates a disabled (i.e.
  invisible) shelf unit across the bridge to block it:

     int &dumb = create_sprite(360, 300, 0, 64, 1);
     sp_hard(&dumb, 0);
     draw_hard_sprite(&dumb);
     sp_disabled(&dumb, 1);

  So the result of all this is that the guy and the invisible shelf unit are
  effectively vision 1: even to the point that to get rid of them when the
  money is paid, he does a force_vision(0)!  So much for not wanting to deal
  with visions...]

  See also: sp_active(), sp_nodraw();
            Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_distance()
# Categories: Enemy Sprite; Sprite Attribute (DinkEdit: none); Weapon
# Prototype:
#   void sp_distance( int sprite, int value );

  Sets the range of the guy's weapon.  At least for Dink.  With monsters with
  touch damage enabled, it [controls how close a monster with a base_attack
  will get before it attacks.]

  [Above correction courtesy of ManaUser, who goes on to explain: "sp_range(),
  which as you said should be listed with this, controls how far the attack
  actually reaches.  So if you used sp_distance(100) and sp_range(50), the
  poor monster would always be attacking before Dink is close enough."
     Actually, I don't feel sorry for the monsters and, from what I have seen,
  this is a pretty typical relationship between sp_range() and sp_distance()
  in monster scripts: it gives the player a fighting chance in a battle
  because now you can see the monster trying to attack Dink before they are
  close enough to hurt each other.

  ReDink1 also thinks Seth was wrong about sp_distance() setting the range of
  Dink's weapon: "After testing, I haven't found anything that sp_distance()
  does for Dink. Sp_range() sets the range of Dink's weapon, not sp_distance(),
  as far as I can tell.  The Debug feature is actually half-way useful in these
  situations: you can see the hardboxes and ranges by just pressing Alt-D."]

  See also: sp_range()

#sp_editor_num()
# Categories: Editor Data; Sprite
# Prototype:
#   int sp_editor_num( int sprite );

  The opposite of sp() - returns the EDITOR # of a given sprite.  Will
  return -1 [actually, 0 -- see below] if sprite did not originate from the
  editor.

  [One of the most common pieces of code in all Dink-dom is this:

     int &hold = sp_editor_num(&current_sprite);
     if (&hold != 0)
         editor_type(&hold, ?); // where ? is 1, 6 or whatever

  The intent of this code is to determine whether the sprite was created in
  DinkEdit.exe or by create_sprite(), and if the former, make the sprite not
  come back for awhile or never, depending on the argument passed to
  editor_type().  It works very well, but it would not be reliable if
  sp_editor_num() returned -1 for non-editor objects.  Experimentation proves
  that sp_editor_num() does indeed return 0 for create_sprite() objects.
     'Course, I still would have preferred to see this code written as,
  "if (&hold > 0) ..."]

  See also: sp()


#sp_exp()
# Categories: Enemy Sprite; Sprite Attribute (DinkEdit: none)
# Prototype:
#   int sp_exp( int sprite, int value <-1 to not change> );

  How much experience someone [Dink/sprite 1 only] would get if they killed
  'em.  [Unless, that is, the die() procedure does something weird like setting
  sp_brain to 12 to make the monster appear to explode or shrink away to
  nothing.  In this case, the sprite's "dying" is not perceived to be the
  result of Dink killing it and so the sp_exp() value is not added to &exp:
  you have to do that explicitly with add_exp().

  Interestingly, even though die() is run when the sprite is supposed to have
  "died" (duh!), and even though sp_xxx() commands are supposed to return -1
  (or 0) in this case, the monster's sp_experience() can be retrieved just
  fine in a die() procedure -- see add_exp() for example code. ]

  See also: Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_flying()
# Category: Sprite Attribute (DinkEdit: none)
# Prototype:
#   int sp_flying( int sprite, int value <-1 to not change> );

  1 means this sprite can move over 'low hardness' like water.

  [ManaUser: "Good for missiles; works, but not well, on Dink and monsters."]


#sp_follow()
# Categories: Sprite Attribute (DinkEdit: none); Undocumented
# Prototype:
#   int sp_follow( int sprite, int sprite_to_follow <-1 to not change> );

  ManaUser: "Works kind of like sp_target(), but they won't attack."

  [The difference is that while sp_target() causes the sprite to zero right
  in on the targeted sprite, sp_follow() tries to cause a following sprite
  to maintain a distance of 30 pixels on both coordinates from the followed
  sprite.  The following sprite will not move to increase the distance
  however, only to decrease it.

  See s1-duck.c and s3-freak.c as released by RTSoft for example usage.]

  See also: sp_target(); brain 15 of sp_brain()


#sp_frame()
# Categories: Animation; Sprite Attribute (DinkEdit: none)
# Prototype:
#   int sp_frame( int sprite, int value <-1 to not change> );

  Frame of the sequence of the ANIMATION this sprite is playing, not valid
  if 'seq' is set. (seq is the animation)

  [I have no useful information about sp_frame().  If it does anything at all,
  I can't tell.  Consider, for example, this line from RTSoft's 1gold.c:
      sp_frame(&current_sprite, 4); //so the seq will start
  Now, script "1gold" exists to be attached to a gold coin so Dink will pick it
  up if he touches it.  There is no animation: the pseq/pframe for a gold coin
  is 178/1; there are only four pframes in pseq 178; and the other pframes are
  for piles of 10, 25, and about 50 coins, respectively.  1gold.c does not, and
  would not want to, animate sequence 178 or show any (p)frame other than 1.  I
  can only conclude that the command does nothing, at least not in this context.

  ManaUser tried to help me out at one point.  Perhaps some of his notes will
  help: "If sp_seq() is zero (the default), then the sprite is shown with frame
  sp_pframe() of sequence sp_pseq().  If sp_seq() is NOT zero, then the sprite
  will be shown with frame sp_frame() of sequence sp_seq(), and sp_frame() will
  change [at the currently set sp_frame_delay() number of milliseconds].  ...If
  you change [sp_frame()], the picture won't change right away but the *next*
  frame shown will be the one following the one you set it to.
     I cannot confirm any of this.  Neither sp_frame() nor sp_Pframe(), whether
  used before or after sp_seq(), had any effect on the animation when I wanted
  to make three water fountains (seq 94) animate out of sync.  It does work in
  mag-star.c, however, so I can't explain it.]

  See also: sp_seq(), sp_pframe(), sp_frame_delay();
            Part 2A, "Commands To Get/Change Sprite Attributes";
            Part 2B, "About PSEQ and PFRAME"


#sp_frame_delay()
# Categories: Animation; Sprite Attribute
# Prototype:
#   int sp_frame_delay( int sprite, int value <-1 to not change> );

  If not 0, this forces all animations on this sprite to play at this delay
  in 1000th's of a second between frames.  This lets you 'turbo charge'
  monsters, so the walking animation will match the extra fast speed.

  [Notes:
  (1) My notes indicate I have had mixed success using sp_frame_delay() for
      brain-6 animations, though my recent experience is that it works fine.
  (2) What does a delay of 0 mean?  In general, smaller numbers yield faster
      animation than larger numbers; but the Ultimate Cheat proves that 0 is
      slower than 30.
    ManaUser: "In this case, zero means default.  This is set in Dink.ini for
      some sprites... Some sprites.  So what's the 'default default' if they
      don't set it?"  I have no answer for ManaUser's question yet.
  (3) Seth lost me with that sentence about turbo-charging monsters, but
      ManaUser's comments and my own research have helped to sort it out a
      little.  If you want to create a fast monster, you can experiment with
      sp_speed() settings.  This command can make the monster move across the
      screen faster but has no effect the animation.  Once the speed is the
      way you want it, experiment with non-zero frame delays until the
      monster's animation speed is again in keeping with its movement speed.
  (4) Does this command only apply to the "walking" animation?  What about the
      monster's fighting speed?  Well, in the first place, the sp_timing() and
      sp_frame_delay() settings apply whether the monster has targeted an enemy
      or not.  But the monster's attack frequency is typically controlled by
      sp_attack_wait() or set_callback_random(); whatever mechanism is used can
      be tweaked independently of the monster's walking speed. ]

  See also: sp_seq()/sp_frame(), sp_timing(), sp_base_walk(), sp_base_attack();
            category "Enemy Sprite" commands;
            Part 2A, "Commands To Get/Change Sprite Attributes";
            Part 2B, "About PSEQ and PFRAME"


#sp_gold()
# Category: Sprite Attribute (DinkEdit: none), Undocumented
# Prototype:
#   int sp_gold( int sprite, int value <-1 to not change> );

  [ManaUser: "Doesn't do anything, but you can store a number in it. :)
  Clever scripters may find uses for this."

  (Perhaps they could, ManaUser.  It looks like it may have been part of a
  half-developed idea where a sprite such as a monster or treasure chest could
  have had its contained gold set in DinkEdit; then relatively generalized
  scripts could have used this function to determine that amount and transfer
  it to Dink when appropriate.  Since this attribute is not accessible in
  DinkEdit, then scripts are free to use it as desired.  Unfortunately, the
  value returns to zero every time Dink leaves the screen or it is otherwise
  re-drawn, so about the only value is to communicate between two or more
  scripts.
     I used it in a situation where I wanted one of two enemies -- but not
  both -- to do something on a certain screen, so I included code like this
  in the main() procedure of an "enemy" script (en-bonc.c or something):
     if (&player_map = <some map number>)
     {
        int &otherguy = get_sprite_with_this_brain(9, &current_sprite);
        if (&otherguy > 0)
        {
           int &didit = sp_gold(&otherguy, -1);
           if (&didit == 0)
           {
              sp_gold(&current_sprite, 1);
              // (do it, whatever "it" was)
           }
        }
     }

  See also:
    sp_brain_parm() and sp_brain_parm2, which can often also be used
       to store temporary values; and
    editor_seq() and editor_frame(), which can be used with care to
       store values more permanently.


#sp_hard()
# Category: Sprite Attribute
# Prototype:
#   int sp_hard( int sprite, int value <-1 to not change> );

  If 0 then this sprite's hardbox will be added to the hardmap.  If 1, it
  will not be.  Moving sprites should be 1.  A call to draw_hard_map() [or
  draw_hard_sprite()] must be made to implement any changes.

  [Notes:
   (1) Beware! More than one dmod author has noted that these values seem
       reversed, like the command would have been better named sp_NOThard().
   (2) The prototype has been adjusted -- the original document did not
       indicate that this command does indeed return the sprite's current
       sp_hard() setting when <value> is -1.
   (3) There are no script commands to manipulate a sprite's warp property,
       but as long as the warp property has been set in DinkEdit, it can be
       deactivated by turning off its hardness (value=1) and re-activated
       by turning its hardness back on (value=0).  See Tyrsis' note in
       sp_active().
   (4) Tyrsis also notes that "burnable" trees (sequence 32, frame 1) lose
       their hardness when they are hit by a fireball, even if their script
       does not allow them to be burned. This is due to the action of the
       dam-fire script, which includes this code:
         sp_hard(&missile_target, 1);
         draw_hard_sprite(&missile_target);
         &junk = is_script_attached(&missile_target);
         if (&junk > 0)
         {
           run_script_by_number(&junk, "die");
           return;
         }
       So when she used burnable trees to block a path, she attached scripts
       that include this code:
         void hit(void)
         {
           sp_hard(&current_sprite,0);
           draw_hard_sprite(&current_sprite);
         }
         void die(void)
         {
           sp_hard(&current_sprite,0);
           draw_hard_sprite(&current_sprite);
         }

  (end of sp_hard() notes) ]

  See also:
    sp_active(), for an example of this command in action;
    draw_hard_map(), draw_hard_sprite()


#sp_hitpoints()
# Category: Sprite Attribute (Dinkedit: Shift 8)
# Prototype:
#   int sp_hitpoints( int sprite, int value <-1 to not change> );

  Hitpoints of sprite.  If 0, nothing happens when this sprite [is hit, it]
  cannot die.  In both cases [that is, whether "value" is 0 or not], if a
  script is assigned to the sprite, hit() and die() will be run. (well, die()
  is run when their hitpoints < 1, naturally).

  [In plain English:
    1) If hitpoints are set greater than zero, the sprite can generally be hurt
       by getting hit and will "die" (have its die() procedure run) when its
       hitpoints become 0 or lower.
       ManaUser: "Dink always has 0 hitpoints, he uses &life instead."
    2) If hitpoints are set to 0, the sprite is invincible.  Hits do not affect
       its health and its die() procedure is never run.  However:
    3) Regardless of a sprite's hitpoint setting, its hit() procedure is run
       when it is hit.]

  See also:
    hurt()         [for another way to change a sprite's hitpoints]
    sp_defense()   [for a description of the interaction between a sprite's
                      attributes when it is hit or otherwise hurt]
    sp_nohit()     [to prevent a sprite with hitpoints from being hit]
    Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_kill()
# Category: Text Sprite, Sprite Attribute
# Prototype:
#   void sp_kill( int sprite, int delay_before_killing );

  This lets you tell [a] sprite to kill itself in a timed way.  [Example:]
     sp_kill(&current_sprite, 1000);
  would cause &current_sprite to die in 1 second. (0 to disable, use on text
  to make it stay there).

  ["delay_before_killing" is expressed in thousandths of a second, of course.
  As for the rest, with some input from ManaUser, I think I can now correctly
  interpret, "0 to disable, use on text to make it stay there."
    a) All the say() functions create what is called a "text sprite", a sprite
       pretty much like any other with two exceptions:
       1) Instead of a graphic, a text sprite is displayed as text (duh); and
       2) A text sprite has an implied sp_kill() command applied to it immed-
          iately when it is created, so by default it only lives a preset
          amount of time (2700 milliseconds for strings up to 35 characters
          long, increased with each additional character to a maximum of 7700
          milliseconds for a maximum-length string of 100 characters).
    b) But: unless say_stop() or say_stop_xy() is used to create the text
       sprite, the script continues to run while the text sprite lives, and so
    c) Another sp_kill() can be issued to override the first.  For example:
          // display a warning, centered at the top of the screen
          int &txt_sprite_num = say_xy("This is the lions' den, Dink!", 0, 0);
          // make the warning stay until Dink leaves the screen
          sp_kill(&txt_sprite_num, 0);
  "sp_kill(<sprite>,0)" is in fact the default status of most (non-text)
  sprites, and whether applied explicitly or by default, it does not in and
  of itself mean the affected sprite is invincible.  It just means it won't
  die automatically when a timer runs out.

  How do you reverse the effect of an "sp_kill(<sprite>, 0)"? The obvious way is
  to give it a non-zero time and let it die that way: "sp_sprite(<sprite>, 1)".
  Assuming it is a text sprite, however, and you need to kill it so you can re-
  place it with an updated version of itself, this may not kill it fast enough.
  It's usually not a big deal, but the new sprite could easily be created before
  the old one has time to die.  A "wait(1)" can then be issued to wait for it to
  die.  But the easiest way seems to be "sp_active(<sprite>, 0)".]

  See also:
    say(), etc.      [sp_kill() is used most often with text sprites]
    create_sprite()  [next most frequent association with sp_kill()]
    sp_active()      [faster than sp_kill() to kill a sprite right now]
    sp_brain()       [brains 5, 7, 12, and 17 are alternate ways to limit
                        a sprite's lifespan]


#sp_kill_wait()
# Category: ???
# Prototype:
#   void sp_kill_wait( int sprite );

  This is the delay created by sp_kill.  Sometimes you want to change this to
  0, so a sprite will react immediately.

  [This explanation is pure nonsense to me.  I have no idea what it means or
  what this function does.  Nor does looking for example uses of this command
  help.  The only example I can find is this line in every weapon's item-xxx
  script:
     sp_kill_wait(1); //make sure dink will punch right away
  If this command were not there, under what circumstance might Dink not "punch
  right away?"  If anyone knows, I'm listening.  These theories have been
  explored and proven false:
  (1) That sp_kill_wait(<sprite>) is the replacement for sp_kill(<sprite>,0)
      not meaning "kill immediately".  This sp_kill_wait() command has no
      discernible effect in this context:
         int &ts = say_xy("This text lasts for one second...", 0, 0);
         sp_kill(&ts,1000);
         wait(300);
         sp_kill_wait(&ts);
  (2) That sp_kill_wait() might address "the delay created by" sp_attack_wait(),
      not sp_kill().  This does not seem to be the case either, however.]


#sp_move_nohard()
# Category: Sprite Attribute (DinkEdit: none)
# Prototype:
#   int sp_move_nohard( int sprite, int value <-1 to not change> );

  1 means this sprite will not be stopped by any hard obstacles.

  [ManaUser: "Won't work on Dink, may not work at all."  I have to concur --
  I have had no luck getting this command to work under any circumstances,
  nor (and this is always the kiss of death) can I find any example of its
  use in RTSoft source code.]
  
  See also: sp_flying() [same concept and works, mostly]


#sp_mx()
#sp_my()
# Category: Sprite Attribute (Dinkedit: none)
# Prototype:
#   int sp_mx( int sprite, int value <-1 to not change> );
#   int sp_my( int sprite, int value <-1 to not change> );

  X (and Y) movement.  If you don't want to deal with directions, or need some
  kind of weird movement, [these let] you specify in pixels how fast the sprite
  is going.  [Negative values] move left [for sp_mx(), up for sp_my()].
  [Positive values] move right [for sp_mx(), down for sp_my()].

  [Notes:
   (1) I originally questioned whether the "<-1 to not change>" was right,
       but ManaUser assures us that it is: "Annoyingly enough, -1 really will
       not change the value.  This means you cannot make a sprite move up or
       left one pixel at a time.  Other negative values do work, however."
   (2) sp_mx() and sp_my() override and effectively replace the sprite's
       sp_dir() and sp_speed() values.  By default, those two commands set
       these two as follows:

          sp_dir()   default sp_mx()   default sp_my()
          --------   ---------------   ---------------
             1        - sp_speed()      + sp_speed()
             2              0           + sp_speed()
             3        + sp_speed()      + sp_speed()
             4        - sp_speed()            0
             6        + sp_speed()            0
             7        - sp_speed()      - sp_speed()
             8              0           - sp_speed()
             9        + sp_speed()      - sp_speed()

   (3) As noted with sp_speed(), sp_timing() controls how often the sprite
       moves the sp_mx() and sp_my() number of pixels.
   (4) Certain brains, missile brain 11 for sure, randomly set the default
       sp_mx()/sp_my() to twice sp_speed(); however, if sp_mx() and sp_my()
       are set explicitly, the brain does not change them.
   (5) Brain 1, Dink, is an exception: like sp_speed(), it ignores sp_mx()
       and sp_my().]

  See also: sp_speed(), sp_dir(), sp_timing(); set_dink_speed();
            Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_noclip()
# Categories: Screen Drawing;
#             Sprite Attribute (DinkEdit: none [related to Z or X plus arrows])
# Prototype:
#   int sp_noclip( int sprite, int value <-1 to not change> );

  If 1, this sprite will not be clipped in the normal game window.  (if you
  really want a sprite on the status bar, this is the way to do it..)

  [This is the only mention of "clipping" in this whole document.  In DinkEdit,
  a sprite can be "clipped" from the top, bottom, or either side, and it's
  analogous to just taking a pair of scissors and trimming off part of the
  graphic.  So apparently, if any part of a sprite's graphic appears in the
  status area, draw_status() automatically clips the graphic from the bottom,
  unless sp_noclip() is used to suppress this action.
     Note what happens if the graphic appears *entirely* in the status area.
  In this case, the scissors analogy fails, because the sprite is visibly
  gone entirely.  This can also be done in DinkEdit, and question 25 of
  "D-Mod Editing FAQ v1.3" by Mike Snyder (follow the "Development" link at
  http://rpgplanet.com/dink/ ) recommends it as a way to solve an occasional
  problem with scripts and invisible sprites.  My latest research indicates
  this is dated advice, however, that sp_nodraw() is now the preferred way to
  make a sprite invisible.]
    
  See also: draw_status()


#sp_nocontrol()
# Categories: Animation; Weapon
# Prototype:
#   int sp_nocontrol( int sprite, int value <-1 to not change> );

  If 1, this sprite cannot control itself until the current sequence it is
  on is over.  This is [only] used for Dink's weapon scripts right now.

  [This is common sword text, taken from, in this case, item-sw1.c as released
  by RTSoft:
     &basehit = sp_dir(1, -1);
     &basehit += 100; //100 is the 'base' for the hit animations, we just add
     //the direction
     sp_seq(1, &basehit);
     sp_frame(1, 1); //reset seq to 1st frame
     sp_kill_wait(1); //make sure dink will punch right away
     sp_nocontrol(1, 1); //dink can't move until anim is done!
  These lines are in clear violation of Seth's remarks labeled, "BE CAREFUL
  WITH YOUR COMMENTS" in "PART 7: KNOWN LIMITATIONS", but that is an aside.
  Since the script nowhere includes an "sp_nocontrol(1, 0)" command, it would
  appear that "sp_nocontrol(1, 1)" remains in effect only so long as the
  specified sp_seq() is being played.  Effectively, it frees the script from
  having to do a freeze(1) before the sp_seq(), then a wait() and an unfreeze()
  after, with the wait() carefully calculated to pause the same length of time
  as the animation takes to play.

  I have seen sp_nocontrol(1,1) applied to Dink in warp scripts, when playing
  the animation of Dink crawling through a hole (sequence 452), but it appears
  to have no effect beyond that of the freeze(1) command which was used
  first...]

  See also: freeze()


#sp_nodraw()
# Categories: Screen Drawing; Sprite Attribute (DinkEdit: none,
#               but similar to undocumented 2 value for unshifted 2)
# Prototype:
#   int sp_nodraw( int sprite, int value <-1 to not change> );

  The sprite will behave like normal except for one small thing... you can't
  see it. (change to 1 to activate this).

  [In a warp proc, you can use sp_nodraw(1,1) to keep Dink from "popping"
  to his new location when set by sp_x() and sp_y().  Don't forget to do
  sp_nodraw(1,0) after the load_screen() and draw_screen().
     If you are using fade_down() and fade_up(), sp_nodraw() is superfluous
  as long as the fade_down() is before the sp_x() and sp_y() -- and there is
  a wait() sufficient to let fade_down() darken the screen.]

  See also: sp_active(), sp_disabled()


#sp_nohit()
# Categories: Sprite Attribute (Dinkedit: Shift 9); Undocumented
# Prototype:
#   int sp_nohit( int sprite, int 0_or_1 <-1 to not change> );

  [Counterpart of DinkEdit "nohit" property, set with Shift+9.  When (sp_)nohit
  is set to 1, the result is about the same as setting sp_hitpoints(0): the
  sprite is not hurt when it is hit, so its die() procedure is never run(*);
  but its hit() procedure is still run.  The differences are:
    (1) (sp_)hitpoints retains its value and does not have to be remembered in
        a variable or set arbitrarily if it is later desired to make the sprite
        mortal again.
    (2) ManaUser notes that when a sprite has the nohit property set, either in
        DinkEdit or by this function, the sprite is not hit at all by a magic
        attack. A fireball, for example, passes right over the sprite as though
        it wasn't there.
           Considering the name of the property, difference (2) was probably
        intended to apply to hits with a melee weapon as well, but there's a
        bug...
           ManaUser again: "I thought of a way the apparent bug with no_hit()
        could be useful.  That is, hit() runs when the sprite is hit with a
        weapon, but not a missile.  This could be useful, since the dink engine
        lacks any really good way to check which of the two something was hit
        with."
           True enough, ManaUser -- see compare_sprite_script() and compare_
        weapon() for more discussion.  Unfortunately, I have to invoke my "never
        rely on a bug" rule, because this bug has a bug.  Ever play "Friends
        Beyond 3"?  Remember the scene with Dink in the dungeon?  The guard is
        supposed to make a smartalec remark if Dink hits him, so he has a hit()
        procedure.  And that procedure locks in a loop if Dink used inferno
        (hellfire) magic on him.  So my first fix was just to apply no_hit()
        to the guy, so he would still smart off if Dink punched him but ignore
        magic attacks.  Guess what?  No effect!  I had to modify the hit()
        procedure to do an "sp_active(&missle_source, 0)" in order to stop
        the loop after an inferno hit, even with nohit() set to 1.

    (*) Exception to the rule that die() is never run: Unlike a sprite with
        zero hitpoints, a sprite with the (sp_)nohit property set can be hurt
        and even killed by the hurt() command.]

  See also:
    sp_defense()   [for a description of the interaction between a sprite's
                      attributes when it is hit or otherwise hurt]
    sp_hitpoints() [to change a sprite's hitpoints; an alternative way to
                      make it invincible]
    hurt()         [for another way to change a sprite's hitpoints, even when
                      nohit is in effect]
    Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_notouch()
# Category: Undocumented
# Prototype:
#   int sp_notouch( int sprite, int value <-1 to not change> );

  [This command was discovered by ReDink1: "doesn't seem to be used...
  It might be only useful like sp_gold, sp_brain_parm, and sp_brain_parm2:
  just another number to have when messing with sprites."  The prototype
  is a guess at this point, more research is needed.]


#sp_pframe()
# Categories: Animation; Sprite Attribute
# Prototype:
#   int sp_pframe( int sprite, int value <-1 to not change> );

  Frame of the sequence of the current pic being displayed.  Change this to
  change the sprite's pic.

  See also: sp_pseq(); sp_frame();
            Part 2A, "Commands To Get/Change Sprite Attributes";
            Part 2B, "About PSEQ and PFRAME"


#sp_picfreeze()
# Category: Animation?, Undocumented
# Prototype:
#   int sp_picfreeze( int sprite, bool 0_or_1 );

  [Seth's explanation, from "The Making of 'Mystery Island'":]
  We needed the giant boat .BMP to move off the screen at one portion of the
  addon. The problem - because it was in a SEQ with other misc. graphics, it
  would PLAY the entire sequence as it moved up.

  I added a NEW script command that causes a SEQ NOT to play any animations,
  so I wouldn't have to give it it's own SEQ each time I wanted to move a
  certain graphic.

  [Example, from S1-GETON.C as released by RTSoft:

     int &ship = sp(3);
     sp_speed(&ship, 3);
     sp_picfreeze(&ship, 1);
     move_stop(&ship, 8, -300, 1);
     sp_nodraw(1, 0);
     sp_picfreeze(&ship, 0);

  I still have no idea what the deal is, however.  The graphic in question is
  seq. 9, frame 1.  When I comment out the sp_picfreeze() commands, the move()
  command shows seq. 8 frame 1!  Since when can't move() show a single-frame
  graphic moving??? It happens all the time when you push a rock, for example.]


#sp_pseq()
# Categories: Animation; Sprite Attribute
# Prototype:
#   int sp_pseq( int sprite, int value <-1 to not change> );

  Sequence of the current pic being displayed.  Change this to change the
  sprite's pic.

  See also: sp_pframe(); sp_frame();
            Part 2A, "Commands To Get/Change Sprite Attributes";
            Part 2B, "About PSEQ and PFRAME"


#sp_que()
# Category: Sprite Attribute (DinkEdit: unshifted 8)
# Prototype:
#   int sp_que( int sprite, int value <-1 to not change> );

  This is the vertical depth que for the sprite.  In most cases, it is set
  to 0, which means use the Y [coordinate] of the depth dot for this. Is some
  cases, (say a cloud) you want to override this.  Change it to 1000 and it
  will be 'on top' of everything.

  [The above terse explanation of depth que left me very confused at first.
  But here is what the Dink engine is really up to, and it makes perfect sense:

  (1) Every sprite has to be assigned a "depth dot", a single pixel that the
      Dink engine regards as the focal point of the sprite.
  (2) Forgetting about the depth-que attribute for a moment: When drawing a
      screen and after laying down all the tiles, the dink engine conceptually
      sorts all the sprites in order by the "Y" coordinate of their depth dots.
      A "Y" coordinate of 0 is the very top of the screen; a coordinate of 479
      is the bottom of a 640x480 screen like dink.exe uses; though any
      coordinate 400 or greater is in the status area.
  (3) The sprite with the lowest Y coordinate is drawn first.  Then, working
      down the screen and to higher and higher Y coordinates, the new sprites
      are drawn, overlaying sprites already drawn where there is overlap.
         (Note that when the screen becomes active and Dink walks onto it and
      perhaps other sprites start moving around, the dink engine has to
      constantly merge the Y coordinates of the moving images with those of all
      the other sprites on the screen, making them overlay sprites with lower
      coordinates and be overlaid by sprites with higher ones.  Bet that
      routine took some debugging...)
  (4) The rub in all this is when a small object like a bowl is supposed to be
      seen as sitting on a large object like a table.  The table's depth dot
      might be at Y coordinate 190; yet the whole image extends far enough up
      from the depth dot that a small sprite like a bowl should only be at
      coordinate 180 to sit in the middle of it.  So the depth que attribute is
      used to override the implied depth on the bowl.  Setting it to some value
      higher than 190 will instruct dink.exe to draw it after the table is
      drawn so it looks like it is sitting on the table, which is what we want.
  (5) How much higher?  Well, the table's hardness box might allow Dink to get
      as close as 30 pixels or so away from its depth dot; so if the table's
      depth dot is 190, Dink's depth when he gets as close to the table as he
      can will be about 220. Therefore, anything that is supposed to be sitting
      on the table must be given a depth que that is between the Y coordinate
      of the table's depth dot and the Y coordinate of the bottom of its hard-
      ness box.  If it's a plate on the table, and there's supposed to be food
      on the plate, then the food's depth que must be higher than the plate's,
      but still lower than the lower extent of the table's hardness.  So in
      this example, you might set the plate to 200 and the food to 205.

  So what about that statement, "Change it to 1000 and it will be 'on top' of
  everything"?  Changing a sprite's depth que to 1000 will put it on top of
  everything whose depth que is 0, since the depth dot's Y coordinate can't
  possibly be higher than 479.  But it will still not be on top of anything
  with an assigned depth que greater than 1000.  Unlike 0, there is nothing
  special about a depth que of 1000.  It's just a number picked out a hat.

  DinkEdit says depth que can be "from 1 to 20000."  Experimentation shows
  the maximum, "on top of everything" value to be 22023.

  As for the minimum value, look at these lines from item-fb.c, where the
  properties of the fireball's shadow are being set:
     sp_que(&mshadow, -500);
     //will be drawn under everything
  So sp_que() is another of those properties that may be set to undocumented
  negative values, as mentioned by ManaUser earlier (see his comment in the
  section, "*** COMMANDS TO GET/CHANGE SPRITE ATTRIBUTES ***"). ]


#sp_range()
# Categories: Enemy Sprite; Sprite Attribute
# Prototype:
#   int sp_range( int sprite, int value <-1 to not change> );

  For use with missiles - increases the 'checking range'.  Put 600 and it will
  hit everything on the screen, always.

  [The above wording is very confusing. Argument <value> is a number of pixels.
  A missile -- that is, a brain-11 or 17 sprite --  will, at least potentially,
  hit every sprite whose depth dot is within <value> pixels of its own depth
  dot.

  ReDink1 also says that sp_range(), not sp_distance(), is used to set the
  range of Dink's melee weapons.  See his comments in sp_distance().

  ManaUser: "As I said by sp_distance(), sp_range() also controls how far a
  monster's attack reaches."

  Note: the range can be zero, meaning the missile has to hit the monster
  exactly.  (This may in fact be the default value, I haven't checked.)
  This is part of what I did in item-dom.c, the script for the Doom Sword
  in my revision of "Friends Beyond 3, the Legend of Tengin":

     void use( void )
     {
        ... [normal sword stuff]
        int &doom_victim = get_rand_sprite_with_this_brain(9, 1);
        if (&doom_victim < 2) return;
        freeze(&doom_victim);
        int &dsave_x = sp_x(&doom_victim, -1);
        int &dsave_y = sp_y(&doom_victim, -1);
        int &doom_guy = create_sprite(&dsave_x, &dsave_y, 11, 197, 1);
        int &doom_str = &strength;
        &doom_str += &strength;
        sp_strength(&doom_guy, &doom_str);
        sp_range(&doom_guy, 0);
        sp_brain_parm(&doom_guy, 1);
        ... [let "&doom_guy" live a moment as a "static missile", then kill it]
     }

  It's just barely possible that the "&doom_guy" sprite will hit two enemies
  anyway, if they are exactly on top of each other; but setting sp_range() to
  zero makes it highly unlikely.]

  See also: sp_distance()


#sp_reverse()
# Category: Animation
# Prototype:
#   int sp_reverse( int sprite, int value <-1 to not change> );

  If 1, this sprite will play the SEQ's in reverse.  Useful is some situations,
  such as the opening menu [where] we play the arrow animations both ways.

  [Examples from start-1.c as released by RTSoft:
     void buttonon( void )
     {
       sp_pframe(&current_sprite, 2);             // "light up" button
       Playsound(20,22050,0,0,0);                 // play sound
       &crap = create_sprite(204, 86, 0, 199, 1); // create arrow
       sp_reverse(&crap, 0);                      // set seq. to forward
       sp_noclip(&crap, 1);                   // don't clip if in status areas
       sp_seq(&crap, 199);                        // play arrow sequence
     }
     void buttonoff( void )
     {
       sp_pframe(&current_sprite, 1);             // "turn off" button
       Playsound(21,22050,0,0,0);                 // play sound
       sp_reverse(&crap, 1);                      // set seq. to reverse
       sp_seq(&crap, 199);                        // play arrow sequence
       sp_brain(&crap, 7);                        // kill when seq. done
     }
  (end of sp_reverse() notes) ]

  See also: sp_seq()


#sp_script()
# Categories: Script Management; Sprite Attribute (DinkEdit: Shift 5)
# Prototype:
#   int sp_script( int sprite, char script_file_name );

  Kills any other script assigned to sprite, and attaches this new one.  Do
  *NOT* include the .C extension.  It IMMEDIATELY runs this script's main()
  before continuing the current script.

  [Return value is a script number, suitable for use with
  run_script_by_number().]

  [I thought at one point that if "&current_sprite" was referenced in the
  attached script's main() procedure, and if the sprite had been created with
  create_sprite(), then the attaching script needed to set &current_sprite to
  the value returned by create_sprite() before doing the sp_script(). This is
  not so, though it did indeed partially fix a problem where the properties
  being assigned by the new sprite's main() were being applied to the wrong
  sprite, to Dink himself it seemed.  Turned out, however, that main() tried
  to use a non-existent variable, not &current_sprite, as an argument in one
  of the sp_xxx() functions it was running, and that apparently got dink.exe
  all confused.]

  [In a dmod I was tinkering with, where I wanted to change only its scripts
  and make no DinkEdit-type changes, I used this in another sprite's script to
  attach a script to a sprite that should have had one attached in DinkEdit but
  didn't.  Works great, but you can't do it this way:
      sp_script( sp(3), "caveman" );  // "3" was DinkEdit's sprite number
  It has to be done in two steps:
      int &man = sp(3);
      sp_script( &man, "caveman" );

  Also note that it is not the best idea for a sprite to replace its own script
  directly, such as with...
      sp_script( &current_sprite, "cavewarp" );
  The above usually works but I have seen it fail.  It is safer to do it this
  way:
      int &cave = &current_sprite;
      script_attach( 0 );  // or script_attach( 1000 );
      sp_script( &cave, "cavewarp" );
      kill_this_task();

  (end of inserted notes) ]

  See also: script_attach(), is_script_attached(), run_script_by_number()


#sp_seq()
# Categories: Animation; Sprite Attribute (DinkEdit: Shift 4)
# Prototype:
#   int sp_seq( int sprite, int value <-1 to not change> );

  Current sequence being used as an animation, 0 if none.  Change this to have
  the sprite play this sequence like an animation.  When finished, it will
  change back to 0 unless the sprite's brain is a 'repeating' brain [brain 6],
  such as [is used by] a woodstove fire.

  [This can all get very confusing.  Remember: (1) "pseq" simply refers to a
  set of up to 50 graphics, and (2) "pframe" is a single graphic in that set.
  Thus, (3) If "seq" is 0, then the sprite is displayed as a single, still
  image.  (4) If "seq" is set non-zero, then that set of frames (which may or
  may not be same as the "pseq" set) is cycled through one time as an anima-
  tion; then "seq" becomes 0 again and the sprite reverts to the single frame
  identified by "pseq" and "pframe".  Unless, that is, (5) if the sprite has
  a brain of 6, then "seq" retains its value and the animation repeats until
  stopped some other way.]

  See also: sp_frame(), sp_pseq()/sp_pframe(); sp_frame_delay(); sp_reverse();
            Part 2A, "Commands To Get/Change Sprite Attributes";
            Part 2B, "About PSEQ and PFRAME"


#sp_size()
# Category: Sprite Attribute (Dinkedit: unshifted 1)
# Prototype:
#   int sp_size( int sprite, int value <-1 to not change> );

  100 is normal.  200 would make this sprite twice its size.  Sizing can cause
  a large performance hit, (unless the card supports hardware transparent
  scaling) so use sparingly.

  [Obviously, "value" is a percentage of the sprite's native size (the original
  size of its graphic) and may certainly be less than 100 to miniaturize the
  sprite.  As for Seth's "large performance hit," so far I have not seen it
  to be a problem.]

  See also: Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_sound()
# Categories: Sprite Attribute (DinkEdit: Shift 7); Sound
# Prototype:
#   int sp_sound( int sprite, int sound# );

  Attaches a sound (by #) to the sprite and repeats it until the sprite
  it killed [or Dink moves on to a new screen].  Uses '3d' sound cueing.
  (forces 22kHz?)

  [Prototype changed to clarify that the second argument is the number of a
  sound loaded by load_sound().  Also deleted "-1 to not change" notation:
  in my experience, any attempt to pass -1 to this command results in an
  immediate termination of the dmod, as though kill_game() had been used.]

  See also: load_sound, playsound();
            Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_speed()
# Category: Sprite Attribute (DinkEdit: unshifted 4)
# Prototype:
#   int sp_speed( int sprite, int value <-1 to not change> );

  Speed of sprite:  If one is still too fast, see sp_timing. (max of 5);
  Example:  Dink uses speed 1, timing 0.  (no timing delay) and moves
  pretty quick.

  Example:

    int &my_speed = sp_speed(1,-1); // gets the speed of Dink
    sp_speed(1,2);                  // sets Dink's speed to 2

  [Notes:
  (1) Higher "value" settings are faster speeds.  In general, the sprite moves
      in its sp_dir() direction "value" pixels per sp_timing() interval.
  (2) The exact interpretation of this attribute seems to be brain-dependent.
      For Dink (brain 1), this attribute is influenced by set_dink_speed() but
      the values are not the same or even predictable. Among other differences,
      higher values are lower speeds for set_dink_speed().  As for sp_speed()
      itself, brain 1 seems to ignore it.
  (3) For missles (brain 11 and probably 17), the number of pixels moved at
      a time seems to be either "value" or twice "value", selected randomly.]

  See also: set_dink_speed(), sp_dir(), sp_mx()/sp_my(), sp_timing();
            Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_strength()
# Category: Sprite Attribute (Dinkedit: none)
# Prototype:
#   int sp_strength( int sprite, int value <-1 to not change> );

  Strength of a sprite.  If a sprite has a strength of 10, he will hit between
  6 and 10.  [See sp_defense() for a complete description of the interaction
  between a sprite's strength and its other attributes in combat.]

  See also: Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_target()
# Category: Enemy Sprite
# Prototype:
#   int sp_target( int sprite, int value <-1 to not change> );

  This is who you want this sprite to attack.  Unless a base_attack is set, it
  isn't going to do much but follow the guy around.

  [ManaUser: "If no base_attack is set, the monster won't attack. It could still
  have touch_damage though."
     My thinking exactly.  As near as I can tell, for pillbugs and slimes,
  "following the guy around" IS their attack.  Contrary to my earlier theory,
  however, for monsters like slayers and boncas, there is an actual attack
  process whose severity is based on the monster's strength, not touch damage
  (I think).
     Still, for one sprite to harmlessly follow another around, make sure
  touch_damage is set to zero.]

  See also:
    sp_follow(), for a better way of making one sprite follow another;
    sp_strength(), sp_defense(), for more information on battles;
    sp_touch_damage() and the other "Enemy Sprite" commands.


#sp_timing()
# Category: Sprite Attribute (DinkEdit: unshifted 5)
# Prototype:
#   int sp_timing( int sprite, int value <-1 to not change> );

  Allows you to set a delay between how often the sprite's brain is called.
  A delay of 33 means 30.3 times a second.  66 means... 15.15!  [and 43
  means... 23.2558!  and 417 means...]  Without using this, everything
  would move entirely too fast.

  [In other words, "value" is the delay time in milliseconds between calls
  to the brain.  A higher value means fewer calls each second to the sprite's
  brain, and vice-versa.  Note that we're talking about the internal, dink-
  engine brain selected by sp_brain() here; not the monster script that is
  also typically called a "brain".  This command does not directly influence
  calls to a script.

  It does, however, directly influence a sprite's speed, whether set by
  sp_speed() or sp_mx()/sp_my().  These commands set the number of pixels the
  sprite will move at a time, with "time" defined as the sp_timing() interval.]

  See also: sp_brain(); sp_speed(), sp_mx(), sp_my(); sp_frame_delay();
            Part 2A, "Commands To Get/Change Sprite Attributes"


#sp_touch_damage()
# Category: Enemy Sprite, Sprite Attribute (DinkEdit: Alt 1)
# Prototype:
#   void sp_touch_damage( int sprite, int value );

  If > 0 then this sprite will cause this much damage to Dink if touched.  If
  -1, this [sprite's touch() function will be run if Dink touches the sprite's
  hardness box.]  (used for picking up hearts by touch, for instance).

  [As stated, touch-damage is inflicted on Dink only, not other sprites.
  Dink's defense is subtracted from every touch; however, as with hurt(),
  there seems to be a 50-50 chance of Dink taking a hit even if his defense
  is greater than the touch-damage value.

  ManaUser: "Touch() gets run if it's a positive number too, like with slimes."
  The only value of touch_damage() that causes touch() not to be run is 0. For
  safety's sake, the main() procedure for any script with a touch() procedure
  should include:
      sp_touch_damage(&current_sprite, -1);
  (unless it's a monster with positive touch damage.) Without this, the touch()
  procedure will be run only if the sprite's touch damage was set in DinkEdit
  (Alt+1).

  More importantly, it seems to be a good idea, if not absolutely necessary,
  for the first (or at least an early) statement in a touch() procedure to be:
      sp_touch_damage(&current_sprite, 0);
  This prevents the touch() procedure from being called repeatedly and locking
  up the game by constantly restarting itself (or something).

  ManaUser: "Important if and only if there's some kind of delay in the touch()
  procedure.  Make sure you set it -1 again at the end of the touch() procedure
  though, unless you really want it to run only once."
     Good points.  By "delay in the touch() procedure," he's referring to the
  use of any commands such as wait(), say_stop(), or fade_up() that run in
  "human perceivable" time and free the dink engine to turn its attention to
  other tasks and scripts.  If touching just increments variables and so forth,
  that happens as fast as the dink engine can process, and without releasing
  its attention to do other things; but it is rare indeed for a touch()
  procedure not to try and do something audible and/or visible.]

  See also: hurt(); "Appendix B: warp-eg"


#sp_x()
#sp_y()
# Category: Sprite Attribute (Dinkedit: set by sprite's placement)
# Prototype:
#   int sp_x( int sprite, int value <-1 to not change> );
#   int sp_y( int sprite, int value <-1 to not change> );

  [Sets / changes] the X and Y coordinates of the sprite.  Based on their
  'depth dot' (the center of [a] person's foot, usually, or base of the tree).

  [In other words, these commands use the depth dot as a "handle" on the
  sprite for moving it.  The sprite simply "pops" to the new location.  For
  animated movement, use move().  The "value" argument may be negative: -1,
  of course, signals sp_x() or sp_y() to return the sprite's current location
  without trying to change it; but any other negative value may be used to
  move the sprite partially or completely off screen.]

  See also:
    move(), move_stop()            [other ways to move a sprite]
    create_sprite(), inside_box()  [commands that need x/y coordinates
                                      which could be supplied sp_x/sp_y()]
    Part 2A, "Commands To Get/Change Sprite Attributes"


#spawn()
# Category: Script Management
# Prototype:
#   int spawn( string name_of_c_file );

  Sort of like [external()] - but calls [the script's] main() and doesn't stop
  the script it was called from, or affect it in any way.  Be careful with
  this... if you create a 'spawn to infinity' loop [that is, a loop that
  executes spawn() an uncontrolled number of times], you won't like it.

  [A script] spawned this way is NOT attached to a sprite, (use sp_script()
  if you wish to do this) and must be killed manually [with kill_this_task()]
  when you are finished with it, or it will ALWAYS be in memory.  (It can live
  past a screen change...) ["Always" is a long time -- actually, it will only
  be in memory until the next time load_game() is used.]

  Returns the script # created, 0 if there was an error.

  [This script number can be used with run_script_by_number() to run procedures
  other than main() in the spawned script.  Among other things, this can be a
  way for some other script to kill the spawned one.  Example:

      &control = spawn("control");

  Later in the script, perhaps in another proc or, if &control is global,
  then in another script...

      run_script_by_number(&control, "kill");

  In control.c:

    void main( void )
    {
      // (script's main processing, probably a loop that seems to run "forever")
    }
    void kill( void )
    {
      // (any desired wrapup activity -- the value of main()'s
      //  "int" variables are all in scope and available; then...)
      kill_this_task()
    }

  Sylvain Beucler: "The argument is not the filename, but a relative path, and
  it is not limited to 12 or so characters."  This allows some scripts in a
  dmod's "story" folder to be moved to a sub-folder.  Example:

      &control = spawn("extra\control");

  Runs script "...dmod\story\extra\control.c". ]

  See also:
    external()              [for another way to invoke an external script]
    kill_this_task()        [for more information]
    run_script_by_number()  [for ways to communicate with a spawned script]


#stop_entire_game()
# Category: Player Input
# Prototype:
#   void stop_entire_game( bool 1_or_0 );

  Set this to 1 and the whole game will be frozen EXCEPT for choice commands.
  I needed this for lraise.c, people didn't much like the fact that they had to
  pick where their skill points were going WHILE doing battle...  [The choice
  command] will unfreeze the game and set this back to 0 automatically as soon
  as something is chosen.

  [Notes:
   (1) stop_entire_game(), as implied by Seth, is designed for use with
       choice().  Follow "stop_entire_game(1);" with any command besides
       choice_start() and you probably will not get the result you want.
       Any other command I would like to pause the game for, such as wait()
       or wait_for_button(), seems to cancel the stop_entire_game(1).

   (2) "stop_entire_game(0)" has the interesting effect of taking a snapshot of
       every sprite on the screen at the moment it is executed.  Here is some
       sample code that will allow you to see this bizarre effect for yourself.
       Call it, say, key-45.c, press the Del key when Dink walks onto a screen,
       and then walk him around to screen:

          void main( void )
          {
            int &curmap = &player_map;
          loop:
            wait(100);
            stop_entire_game(0);
            if (&curmap == &player_map)
              goto loop;
            kill_this_task();
          }

   (3) lraise.c indeed does a stop_entire_game(1).  At one time, I thought
       there might be a serious danger of freezing the game permanently if
       a dmod allowed Dink to achieve high levels: the stop_entire_game(1)
       command is done unconditionally, but the choice() only happens if
       Dink's current level is 31 or lower.  Considering note (1), however,
       this probably would not cause the game to freeze at all.

   (4) A wait(1) right after the choice_end() is often helpful or even
       necessary, to allow dink.exe a moment to do some cleanup.  It isn't a
       problem in lraise.c, but a couple of glitches are possible.  (1) If you
       do a stop_entire_game(1), a choice menu, and then stop_entire_game(1)
       and another choice() menu, the first menu will not be erased before the
       second one is shown.  (2) Suppose you defined a "bonus" level in your
       dmod, where Dink could choose an attribute to be increased by 10 one
       time.  Without a wait(1) after the choice, the new value would just be
       shown instantly, instead of using the clever animated counter routine
       dink.exe usually uses to change these values.

   (5) Here is a more useful key-## script showing stop_entire_game() at work:

          void main( void )
          {
            wait(1);
            stop_entire_game(1);
            choice_start()
            "(game paused - Ctrl to continue)"
            choice_end();
            kill_this_task();
          }

       I think it is a superior use of the P key over the Pillbug-sound key in
       the Dink\story directory. Delete (or just rename) key-80.d and call this
       one key-80.c.]

  See also: choice()


#stop_wait_for_button()
# Category: Player Input
# Prototype:
#   void stop_wait_for_button( void );

  If you need to give back normal gamepad/keyboard control [after issuing a
  wait_for_button() command] for some reason (like the guy died or something)
  call this.  What happens to the script that called it to begin with?  Good
  question. [see below for a good answer.]

  [This command, stop_wait_for_button(), cancels any wait_for_button() going
  on in any *other* active script, or at least in another procedure.
 
  This makes it an unusual command: it is designed to affect only a command
  issued by *another* script.  There is no use for a procedure to try to issue
  a stop_wait_for_button() on its own wait_for_button(), because that command
  freezes its own procedure until a button is pushed.

  It may be a good idea to use stop_wait_for_button() in a script that does a
  wait_for_button(); but to do it first, before doing the wait_for_button(),
  so that it will be the only script trying to process buttons.
 
  And what is the answer to Seth's "good question"?  It's this: the script is
  terminated as though it had done a "return".  No "kill_this_task()" is done,
  so the script remains in memory.  Therefore, to minimize the implications of
  this, wait_for_button() should be avoided in any script that is started by
  spawn() or does a script_attach(1000), since these scripts survive a screen
  change and would remain in memory indefinitely if terminated by some other
  script's stop_wait_for_button().  However, see the spawn() command for a
  technique that could be used to kill the wait_for_button() script.]

  See also: wait_for_button()


#stopcd()
# Category: Sound
# Prototype:
#   void stopcd( void );

  Stops the CD player.

  See also: playmidi() [for ManaUser's notes on how to play CD tracks]


#stopmidi()
# Category: Sound
# Prototype:
#   void stopmidi( void );

  [Prototype shown for this command was originally:

     void stopmidi( char midiname[12] );

  ManaUser: "The syntax for this command is really [as now shown].  You can't
  play more than one MIDI at a time after all; stopmidi() simply stops which-
  ever one is playing."  ManaUser goes on to say a <midiname> can be coded if
  desired, as shown in the original prototype, but the argument is ignored.]

  See also: playmidi(), turn_midi_off()


#turn_midi_off()
# Category: Sound
# Prototype:
#   void turn_midi_off( void );

  This disables midi's attached to screens. (good if you are using a different
  kind of music and don't want the screen attached midi's to ruin it.)

  See also: playmidi(), turn_midi_on()


#turn_midi_on()
# Category: Sound
# Prototype:
#   void turn_midi_on( void );

  Undoes the above.  This is also called [automatically, by the dink engine]
  when a game is loaded.

  See also: stopmidi(), turn_midi_off()


#unfreeze()
# Category: Cut Scene
# Prototype:
#   void unfreeze( int sprite );

  [For use with freeze(), allows the sprite to move again.]

  See also: freeze()


#user-defined functions -- see entry "my_proc()"

#variables -- see int, make_global_int()

#wait()
# Category: Cut Scene
# Prototype:
#   void wait( int amount );

  Stops this script for this [amount of time] in thousandths of a second.

  [wait() has two categories of uses, one obvious and the other much more
  subtle.  Both are important.

  The obvious use is to suspend the execution of a script for awhile.  It is
  not uncommon to include a short wait() after every say_stop() in a conversa-
  tion, for example, to slow it down a little and make it more realistic.  Or
  perhaps you want to create a potion that would dramatically increase Dink's
  defense for a minute.  The script for such a potion might include:

     void use( void )
     {
        &defense += 50;
        wait(60000);
        // (one-minute wait)
        &defense -= 50;
     }

  The other use of wait() pertains to the fact that the while the dink engine
  tries to do many things at once, that is just an illusion.  Modern computers
  can run huge numbers of instructions per second, but still only one at a time.
  Operating systems such as Windows have very sophisticated methods (not all of
  them successful...) of making all running programs "take turns" using the CPU.
  Within the dink engine, the strategy is more straightforward.  Once it starts
  running a script, for example, that's all it does -- until the script either
  ends or it runs a command that releases the dink engine temporarily to turn
  its attention to other tasks.  wait() is most important of these commands.

  So any wait(), even a wait(1), makes a script "yield the floor" to other
  active scripts to allow them time to finish or at least get to their own
  wait() points before this script is resumed.  See "Part 6 - Overview of
  Script Execution", for a discussion of why this is an important trick to
  know and understand.]


#wait_for_button()
# Category: Player Input
# Prototype:
#   void wait_for_button( void );

  This lets you control the input to do certain things. Used in the shark event
  in Mystery Island.

  After this is called, the script will wait for a button to be pressed.

  The value is in &result.

  Here is [a key-## script] that shows what the values mean:

    void main( void )
    {
      freeze(1);
      say("wait_for_button() active.  Press Esc to quit.", 1);
    wfb_loop:
      wait_for_button();
      //input is read from keyboard and gamepad

      if (&result == 1) say("You pressed button 1 [or the Ctrl key]", 1);
      if (&result == 2) say("You pressed button 2 [or the Space bar]", 1);
      if (&result == 3) say("You pressed button 3 [or the Shift key]", 1);
      if (&result == 4) say("You pressed button 4 [or the Enter key]", 1);
      if (&result == 5) say("You pressed button 5 [or the Esc key]", 1);
      if (&result == 6) say("You pressed button 6 [or the '6' key]", 1);
      if (&result == 7) say("You pressed button 7 [or the '7' key]", 1);
      if (&result == 8) say("You pressed button 8!", 1);
      if (&result == 9) say("You pressed button 9", 1);
      if (&result == 10) say("You pressed button 10", 1);

      if (&result == 12) say("You pressed DOWN", 1);
      if (&result == 14) say("You pressed LEFT", 1);
      if (&result == 16) say("You pressed RIGHT", 1);
      if (&result == 18) say("You pressed [UP]", 1);

      if (&result != 5) goto wfb_loop;
      unfreeze(1);
      kill_this_task();
    }

  [Notes:
   (1) As shown above, freeze(1) should be in effect when using this command.
       If not, Dink still reacts to the arrow keys by moving in the direction
       indicated, and still reacts to the Shift key by using his currently-armed
       magic.  Interestingly, however, he does not still react to the Ctrl key,
       so this command is not well suited for a routine that runs concurrently
       with a fight. (Unless you wanted to force Dink to use only magic in the
       fight...)
   (2) Even though the M key is supposed to be the keyboard equivalent of
       "button 6", this command does not respond to the M or any other letter
       key.  The latest release of Dink, version 1.7, does treat the 6 key as
       button 6, however.  (main keyboard only, not the keypad.) ]

  See also: stop_wait_for_button; freeze()


    **********************************************************************
   ********* Part 2A: "Commands To Get/Change Sprite Attributes" **********
    **********************************************************************

  Seth: "A negative # means 'do not change' in these values."

  Not true.  Here are the facts:

  (1) Typically, but not for every sp_xxx() command, a <value> argument of -1
      is a request to return the current value of the attribute it controls
      rather than change it.  For example:

         int &hold_brain = sp_brain( &current_sprite, -1 );

      This statement defines a temporary variable and stores the "brain" type
      of sprite &current_sprite.

      Exceptions include sp_touch_damage(), where -1 has special meaning,
      sp_sound(), which will abort the whole game if passed -1, and more.
      Consult the command's entry in the "Alphabetical List of Commands" for
      currently known details.

  (2) Do not expect a function to *both* return the current value and change
      it, as would be the case in real C.  This statement...

         int &hold_brain = sp_brain( &current_sprite, 0 );

      always returns 0, regardless of the sprite's current brain.  To both
      remember the sprite's current value and change it, two statements
      are required:

         int &hold_brain = sp_brain( &current_sprite, -1 );
         sp_brain( &current_sprite, 0 );

  (3) Only -1 is a signal to return the sprite's current setting.  The effect
      of other negative numbers varies from command to command.  For example,
      the statement...

         int &hold_brain = sp_brain( &current_sprite, -2 );

      returns -2.  In fact, it would appear that the return value of sp_brain()
      is always the value of the second argument, unless the second argument is
      exactly -1.

  (4) ManaUser: "In my experience, most sp_xxx() values can be set to a minus
      number (besides -1)."  There are several examples of this: sp_x()/sp_y(),
      and sp_mx()/sp_my(), sp_defense(), sp_que(), and surely more.  ManaUser
      indicates that even sp_brain() stores negative values, though they cause
      the sprite to behave similarly to brain 0 since there are no brains for
      those numbers.  "I've yet to find a single command that treats all
      negative numbers as 'do not change'."

  Seth: "A return value of -1 means the sprite does not exist (most likely),
  or there was an error of some kind.  A good way to check [whether] something
  is dead or not."

  ManaUser: "0 is used for this other times it seems."  True.  An excellent
  example is sp_editor_num().

  Commands originally appearing in this section, in order:

     sp_speed()          sp_strength()        sp_pseq()
     sp_dir()            sp_defense()         sp_pframe()
     sp_mx()             sp_exp()             sp_seq()
     sp_my()             sp_sound()           sp_frame()
     sp_disabled()       sp_timing()          sp_frame_delay()
     sp_size()           sp_kill()            sp_base_walk()
     sp_hitpoints()      sp_kill_wait()       sp_base_attack()
     sp_nohit()          sp_x() / sp_y()


    **********************************************************************
   ******************* Part 2B: "About PSEQ and PFRAME" *******************
    **********************************************************************

  [Seth:] We never say "Show pic 788 for this sprite".  Instead we tell it
  which sequence, then what frame.  This means you can add frames to any
  sequence (max of 50) and not 'throw off' any other pics/animations in the
  game.

  PSEQ and PFRAME are the CURRENT sequence and frame that sprite is showing on
  the screen - if SEQ is set, this means the sprite is showing this [as an]
  animation.  (PFRAME will change very quickly if this is happening) [that is,
  PFRAME will change according to the currently set sp_frame_delay(), which is
  usually 30 milliseconds or so if you want the human eye to see an animation;
  but it *could* be a much longer time, for whatever reason.]

  A tree or rock will [have] a SEQ set to 0, meaning no sequence [that is, no
  animation.  It will still have a sequence -- as was already said, a sprite's
  graphic is identified by both a sequence and a frame.]  The PSEQ and PFRAME
  will not change [automatically] in this case, [but] you can change [them] to
  show another sprite, etc.  [Well, another graphic for this sprite, anyway.
  What other "et cetera" is there?]

  If you wanted the tree to suddenly burn, you would set its SEQ to whatever
  animation you wanted - now the PSEQ and PFRAME will automatically follow the
  SEQ script and run through the animation!  [Depending] on the brain, it could
  repeat, too.

  Editing the DINK.INI file allows you to add your OWN sequences!!!

  [It is difficult to determine which commands were originally intended to
  be part of this section, which is actually a subsection of the one above.
  These were for sure: sp_pseq(), sp_pframe(), sp_seq(), sp_frame(), and
  sp_frame_delay().  I think the sp_base commands were as well: sp_base_walk(),
  sp_base_attack().  Many more commands followed this heading, but most of
  them look like they pertained only to the section above, not to this one.]



                        -= PART 3: A CHANGING WORLD =-

We want the world to remember changes.  For instance, if Dink [burned] a tree
down, traveled to the other end of the world [or just moved to the next screen]
and then [came] back, the tree should still be burned. The same goes for taking
items, breaking stuff and anything else you can think off.

How do we make the game remember?  Yes, we could assign &vars to everything and
have scripts kill off what has been taken and change stuff, but this would be-
come very tedious.  So we only do that in special spots where greater control
is required.  For little stuff, like keeping hearts we [pick] up from
reappearing, we use another system. [...one that still uses attached scripts
to drive it, just no global variables.]

Screen Commands Issued From The Player's Data File. (SCIFTPDF for short, ha)

The player's save game file is capable of storing one parm, a seq and frame
for EVERY editor sprite in the entire game.  The parm tells the draw_map
command to override what the map data says and do it different.  For example,
we have the barrels' script do this:

[The executable code below is now as it appears in every "bar-??.c" function
in the Dink source released by RTSoft -- ManaUser noted errors in the orignial
version:]

  int &hold = sp_editor_num(&current_sprite);
  if (&hold != 0)
  {
     // this [sprite] was placed by the editor, let's make the barrel stay flat
     editor_type(&hold, 3); 
     editor_seq(&hold, 173);
     editor_frame(&hold, 6);
     // type means show this seq/frame combo as background in the future
  }

Editor_type can be set to the following:
  0 - no change
  1 - kill sprite completely
  2 - draw pic from enclosed seq/frame data as a sprite WITHOUT hardness
  3 - draw pic from enclosed seq/frame data as a BACKGROUND object WITHOUT
      hardness (can be walked on [but not] behind)
  4 - draw pic from enclosed seq/frame data as a sprite WITH hardness
  5 - draw pic from enclosed seq/frame data as a BACKGROUND object WITH
      hardness (can't walk [on or] behind)
  6 - kill sprite, but let him come back after 5 minutes
  7 - kill sprite, but let him come back after 3 minutes
  8 - kill sprite, but let him come back after 1 minute

[See command editor_type() in "Part 2: List of Internal Procedures" for more
information"]

[Editor_seq() and editor_frame()] store the sequence and frame of the new
sprite to be displayed, for instance, a burned tree.  The associated hardbox
will be taken from the sprite's info if 4 or 5 is used.

[I think by Seth's own terminology, these should have been called
"editor_pseq()" and "editor_pframe()", since they define not an animation
but a single still graphic to be used for the sprite.]

You may wish to call a draw_hard_sprite() afterward if hardness has been
changed. [editor types 2-5]

[The original document provided no prototypes for the editor_xxx() commands.
ManaUser offered this response to my question as to whether they provided
return values:]

"Ahh, yes they do. I found this out a while ago, and it's very cool."
According to him, as long as editor_type() remains 0, "you can give editor_seq()
and editor_frame() any non-negative value you want, and then check it later by
using a -1 as the value.  Here's a sample script:"

   // memory test.

   void main( void )
   {
     int &ed = sp_editor_num(&current_sprite);
       //Get sprite's editor number.
     int &hit = editor_seq(&ed, -1);
       //Store sprite's editor_seq in &hit.
     int &talk = editor_frame(&ed, -1);
       //Store sprite's editor_frame in &talk.
     say("I've been hit &hit time(s) and talked to &talk time(s).", &current_sprite);
       //Display current values.
     }

   void hit( void )
   {
     &hit += 1;
       //Add one to &it.
     editor_seq(&ed, &hit);
       //Save new value in sprite's editor_seq.
     say("I've been hit &hit time(s) and talked to &talk time(s).", &current_sprite);
       //Display current values.
   }

   void talk( void )
   {
     &talk += 1;
       //Add one to &talk.
     editor_frame(&ed, &talk);
       //Save new value in sprite's editor_frame.
     say("I've been hit &hit time(s) and talked to &talk time(s).", &current_sprite);
       //Display current values.
   }

ManaUser continues: "I should warn that for some reason I've had mixed results
editor_frame(), but editor_seq() seems quite reliable. More research might be
helpful."
   That comment led to a response by ReDink1, who says these are the ranges of
possible values for editor_seq() and editor_frame():

      editor_seq(): 0 - 65535
      editor_frame(): 0 - 255

   We can confirm this in a roundabout way by doing some math: Seth said above
that "the player's save game file is capable of storing one parm, a seq and
frame for EVERY editor sprite in the entire game."  Hmmm.  There are 768
screens, times a maximum of 100 non-background sprites per screen, times three
values per sprite, for a total of 230,400 values stored in the save file on
behalf of sprites.  If these were stored as full integers, a save file would
have to include 900K of data for these values alone.  But if it compressed
editor_type and editor_frame into one byte each (values 0-255) and editor_seq
into two bytes (values 0-65535), this requires about 300K of the save file's
319K for this data, which is just about what one might expect.

ReDink1 continues: "Pretty limited, unfortunately (I wish they were like the
other integers)... but good enough for some interesting stuff..."
   Indeed.  I like to use this facility to have a "pushable" sprite, like a
rock or bookcase, store its own position:

   void main( void )
   {
     int &rocks_ednum = sp_editor_num(&current_sprite);
     int &rocks_pos = editor_seq(&rocks_ednum, -1);
     if (&rocks_pos == 1)
     // Dink has pushed this rock...
        sp_x(&current_sprite, 400);
   }

   void push( void )
   {
     if (&rocks_pos == 0)
     // Dink has not pushed this rock...
     {
       int &dinks_dir = sp_dir(1, -1);
       // Dink is only allowed to push from left to right in this example:
       if (&dinks_dir == 6)
       {
         sp_speed(&current_sprite, 1);
         say("It's moving!", 1);
         move_stop(&current_sprite, 6, 400, 1);
         draw_hard_map();
         &rocks_pos = 1;
         editor_seq(&rocks_ednum, &rocks_pos);
       }
     }
   }

What is most interesting to me about all this is that you can mess with
editor_seq() and editor_frame() without changing how the sprite is displayed.
According to ManaUser, this remains true as long as editor_type() is 0. ]



                         -= PART 4: HOW ITEMS WORK =-

With DinkC, you can make unlimited items. [Well, virtually unlimited -- each
item has to have a script, but today's hard drives can hold a LOT of them...]
The only limit is Dink can only hold 16 in his inventory at one time.  This
isn't because I was a lazy programmer (for once), but [because] I believe
having to manage your items and not being able to stockpile too many healing
potions will actually make this a better game.
   [This philosophy has a certain validity, but laziness was surely an
important if not overriding consideration despite his protest to the contrary.
Better would have been the ability to scroll the inventory and let the dmod
author decide whether or not to make the player manage his inventory on a
dmod-by-dmod basis.  As it is, there is a huge onus on the dmod author to
support this philosophy by constantly checking for pack overflow and scripting
logic to handle it.
   If Seth really subscribed to this philosophy, he would have provided a way
to drop that infernal pig feed in the original game.  And if he truly believed
in it, he would have created a mechanism whereby the player could be provided
with non-portable containers, such as chests, where excess inventory could be
stored temporarily and retrieved later.  Alas, no such mechanism exists.]

Everything here also applies to magic.  The difference between magic and
items/weapons is you only have 8 magic slots, and a different button is used
to activate magic.  (so, [Dink can carry at most] 24 items at once basically.)

[ManaUser: "Magic also differs in that it's use() procedure runs any time
SHIFT is down and &magic_level is equal to &magic_cost; whereas weapon items
run their use() procedure once (only) each time the player presses Ctrl."
In other words, if Dink's magic recharges fast enough, a magic attack will
repeat automatically as long as the Shift key is held down, but the Ctrl key
has to be released and pressed again to repeat a melee attack.

Back to Seth...]

This means adding a new weapon that shoots an animation of your face across the
screen with it's own sound effect is very easy to do!  [Remember, he didn't say
*creating* the weapon was easy.  But once you create the graphic for the weapon
in the pack, the animation of the weapon in use, its sound effect, and a script
to control every aspect of the weapon's behavior, the final step of adding it
to the game is indeed "very easy to do."]

You can COMPLETELY control the behavior of any item.  First, you need to give
the player the item.

  int add_item(char scriptname[8], int seq, int frame);
  int add_magic(char scriptname[8], int seq, int frame);

The rest is handled by the item's script itself.  The best way to understand
how it works is to look at item-fst.c -- but I'll explain anyway.

The sequence and frame are the picture of the item.  As soon as [an add_item()
or add_magic() command] is called, 'void pickup( void )' is called from the
script.  If [simply] having this item in your inventory does something magical,
[the pickup() function] is the [place] to do it.

When [the player] clicks on it to arm it, the script is loaded into memory and
two procedures from it are called.  First it looks for 'void arm( void )' and
runs this.  This is where you would add 8 to the strength or make or increase
Dink's size by 50 or whatever.

Second, it looks for 'void armmovie( void )' - any special thing like Dink
saying, "wow, I just armed so and so" is said here.  This is ONLY called if
the player arms the weapon.
   [There has been a lot of confusion here, but the facts are pretty much as
Seth stated them, as long as you understand what he is saying.
   (1) "armmovie()" will indeed be called after "arm()" if it is present in
the item's script.
   (2) The emphasis in the second sentence is on the wrong word.  Better would
have been, "This is only called if the PLAYER arms the weapon."  That is,
"armmovie()" is not called if the undocumented arm_weapon() command is used
to arm the weapon: only if the player arms it using the inventory screen.
   (3) There seem to be no examples of armmovie() in RTSoft code. Nevertheless,
it is valid and does work if you can think of a good reason to use it.  More
in a moment.]

When the [item, whether weapon on magic] is DISARMED, it runs 'void
disarm( void )' from the item's script.  Use a kill_this_task() at the
end of it.

[Ok, more about arm(), armmovie(), and disarm(). Seth left out a very important
fact: load_game() runs the disarm() and then the arm() proc in Dink's currently
armed weapon and magic.  It does not, however, run armmovie().  After some
discussion, ManaUser came up with an intriguing idea about this: "...armmovie()
could be used for more than saying stuff.  What if you had an evil sword that
drained one life point from Dink every time he equipped it?  You could put the
code there to make sure he wouldn't be unfairly zapped loading or other
automatic equipping..." (another reference to the arm_weapon() command.)

Back to Seth:]

Here are some other [functions] it will check for [and call if present].  If
any of these don't exist, it assumes you don't need that function and doesn't
sweat it.

void use( void ) - when the button [Ctrl for a weapon or Shift for magic] is
                   pressed, this is called.
void holdingdrop( void ) - run when dropped[*] and item was armed at the time.
                           (drop() will be run also)
void drop( void ) - when item is 'dropped[*]' this is called.

void pickup( void ) - when item is 'picked up[*]' this is called.

[(*) An item can only be 'dropped' by a script that runs one of the commands
kill_this_item()/kill_this_magic() or kill_cur_item()/kill_cur_magic() on it.
These commands are described below.  Likewise, an item can only be 'picked up'
by a script doing an add_item()/add_magic(), as described above.]

Use a kill_this_task() at the end of drop() and pickup() also.  [The idea is
not to have active scripts in memory for everything in Dink's inventory, only
for the one weapon and one magic item currently armed.  "Killed" scripts are
automatically re-loaded from the "story" directory if they are needed again.]

Now, there are times when you may need other commands dealing with items, so
here they are.

int free_items();

   Returns how many free item spots remain.

int free_magic();

   Returns how many free magic spots remain.

int count_item(char name_of_item_script)

  Returns how many [weapon] items with this script name they have.

int count_magic(char name_of_item_script)

  Returns how many [magic] items with this script name they have.

void kill_cur_item( void )

  Kills the currently armed item.  Runs its disarm() script, then [its] drop()
  script.

  [This description is incomplete -- as with all these commands, see the
  command's main entry in Part 2 for more information.]

void kill_cur_magic( void )

  Same as above but for killing the current magic equipped.

void kill_this_item( char name_of_item_script )

  Kills first instance of this item by name of script.  If it's currently
  armed, it will run its disarm() and drop(), otherwise it will just run the
  drop() script and remove it.  [(1) also runs holding_drop() if armed; (2)
  removes the item even it is armed; and (3) unlike kill_cur_item()/magic(),
  does not simulate any unexpected commands or functions.]

void kill_this_magic( char name_of_item_script )

  Same as above but applies to the magic slots.

int compare_weapon( char name_of_item_script)

   Example:  compare_weapon("ITEM-B1"); would return 1 if the armed item's
             script was item-b1.  Used in s3-gobg.c

int compare_magic( char name_of_item_script )

   Works like above.  [No, it doesn't.  It doesn't work at all.]

[See the main entries for all the above commands in Part 2 for additional
information.  See also the undocumented commands arm_weapon() and arm_magic().] that need to be addressed here...



                 -= PART 5: HOW THE CHOICE STATEMENT WORKS =-

This is the dialog box that we pop up so often.  Everything from the load game
menu to talking with NPC's is done with this.  It's pretty simple to use, here
is an example:

   choice_start()
      "Yes"
      "No"
   choice_end()

That is all!  The result will be put into &result.  So:

   if (&result == 1)
   {
      //said yes!
   }

   if (&result == 2)
   {
      //said no!
   }

Now, let's say we want SOME [choices] to [optionally] show up, because we are
too lazy [or just too good a programmer] to make separate procedures for every
possible combination.  Very easy:

   choice_start()
      (&life < &lifemax)   "I need to be healed, I'm hurt"
      (&life >= &lifemax)  "I'm just dandy"
      "Leave"
   choice_end()

Only two options will show up, depending on the player's life.  Option 2 will
still return 2, even if only option 1 is listed, so you can have your IF
statement check for 1 and 2, and just the chosen one will be activated.
[In other words, the value in &return is always based on the total number
of choices between choice_start() and choice_end(), regardless of how many
choices were actually displayed to the player this particular time.]

Note:  These [conditionals] can be stacked:

   (&love != 1) (&life == 5)   "Love isn't 1, and you have five life!"

The choice statement can handle up to TWENTY choices.  If this takes up more
than one screen, it will automatically [handle] scrolling to the next screen
when it needs to.

[ManaUser: "This is not quite true.  First, that means twenty choices available
at once, so if you have, say, 30 choices but most are conditional such that
there are never more than 20 available at one time, you're okay.  Second, the
command doesn't totally fail with more than 20 choices at once, but something
strange happens to the title."]

[At the other extreme, choice() is smart enough to know that if there are no
choice lines, or if all have been suppressed by conditionals, the whole choice
thing is just skipped.  It is not possible to display a choice menu with no
choices.
   Note that &result is left unchanged if the choice menu is suppressed.  If
not showing the menu is a legitimate possibility your code needs to detect,
initialize &result to some flag value like 0 before choice_start().]

We could stop here, but there are a few more things:

You can put special commands at the top of the choice statement to change
how it looks.

   choice_start()
      set_y 240
      set_title_color 15
      title_start();
   Would you like to rock steady?
      title_end();

      "Yes"
      "No"
   choice_end()

Ok, set_y sets the y cord that the choices will begin listing.  [This is a
little complicated.  "set_y 0" puts the choices at the top of the choice box
(not the top of the screen).  At least if the current font is Ariel (the
default), each line on the box is 18 pixels.  There is a 100-pixel "no man's
land" above the set_y setting which is always blank.  Thus, set_y must be at
least 118 to allow one line of title to show; at least 136 to allow two lines
to show, etc. At the bottom, set_y should be no more than 316 to properly
display one choice, no more than 298 to show two, 280 to show three, etc.]

title_start() lets you specify a 'title' for the top.  [All text of all lines
between title_start() and title_end() are displayed.  If the text lines are
quoted, the quotes will be displayed.  If the lines have leading spaces, they
are ignored; but then the text of the line is centered horizontally.  If the
lines contain variable names, their values are substituted.  All this means
that choice() is also a great way to display a box of information for the
player to read; the only "choice" that might be given in this case is "ok".]

set_title_color (optional) lets you specify what color the title is.
(you cannot change the color of the options themselves!)

The colors are the same as the ANSI char set if you know about BBSing...1
through 15. [1=dark blue 2=light green 3=cyan 4=orange 5=lavender 6=ochre
7=light grey 8=dark grey 9=sky blue 10=green 11=yellow 12=yellow 13=pink
14=yellow 15=white.  These are generally the same as for the optional "`"
prefix on the say() commands, with 13 corresponding to "`#", 15 to "`%",
and 11, 12, or 14 to "`!", "`@", or "`$" (all yellow, same as specifying
no color code).  The only notable difference is code 1, which is a light
magenta in say().]

[Choice also supports two pseudo-variables, &savegameinfo and &buttoninfo.
They are "pseudo-variables" because they have no special meaning in any
context except choice().

"&savegameinfo" is used in choice() menus that offer to save the game or load
a previously saved game.  When used, "&savegameinfo" must be the ONLY text
between the quotes in a choice.  "&savegameinfo" in the choice that will set
&result to 1 if selected is replaced with information about SAVE1.DAT; the one
that will set &result to 2 shows information about SAVE2.DAT, etc.

"&buttoninfo" is designed for a choice() menu that allows the player to change
which keys are used to attack, "talk", etc. using the undocumented set_button()
command. (Does anyone actually do this?) Unlike "&savegameinfo", "&buttoninfo"
need not be the only text within the quotes.  "&buttoninfo" in the choice that
will set &result to 1 is replaced with the current meaning of button 1 (Ctrl
key, attack by default), etc.

See ESCAPE.C as released by RTSoft for examples of both of these menus.]



                  -= PART 6: OVERVIEW OF SCRIPT EXECUTION =-

The way scripts can call other scripts, who they are connected to, and when
they die is kind of like time travel -- lots of theory and really hard to
understand.  [Aw, c'mon, it's not that hard.  It just has to be explained
better.]

Attaching a script to an item causes it to be run when the screen is drawn --
this is fine and dandy.  There is ANOTHER kind of script that can be run
BEFORE the screen is drawn, this is the script associated with the MAP SCREEN.
(done by pressing B [for "base script"] in the map editor.)

This script doesn't have the luxury of knowing certain info, like using the
Sp() command will always return 0, because no sprites have been drawn yet.
However, it's a powerful tool when used correctly, [because] you can change
[&vision] and set music BEFORE the screen is entered.

(You can do a wait(1) in a 'screen script', and it will draw all the sprites
and return, so you can manipulate the rest of the data.)

[This "wait(1) trick applies to scripts attached to sprites as well.  I found
this out while tinkering with a dmod that had a script attached to a cave
entrance that was invisible until late in the story.  Its main() procedure
controlled whether the sprite appeared or not, and its talk() procedure made
Dink comment on its appearing if the player thought to "talk" to a cave
entrance.  Well, I also wanted to Dink to make this comment automatically. So
I changed the main() procedure to check whether this was the first time Dink
was walking onto this screen after the cave appeared, and if so, invoked talk().
And the dmod immediately terminated! Funny part was, when I put in debugging
say_stop() commands to see where it crashed, it didn't!  In other words, if
there was a say_stop() for Dink in main() before it called talk(), it worked.
So my original workaround was just to have main() give a shortened version of
his talk() comment without calling talk().  But then something clicked when I
read the above again, and I tried including a wait(1) within the same "if"
that calls talk(), and that also worked fine.]

                  --------------------------------

[Part 6 used to be called, "Advanced Techniques", but outside of the wait(1)
trick, Seth really discussed no advanced techniques in it.  On the other hand,
it seems like he just got into script execution and then left off.  So here
is my attempt at a more complete overview of how scripts are run in Dink. This
took me a long time to sort out, and I suspect it's a source of confusion for
a lot of other wannabee dmod authors as well.

When dink.exe, the "dink engine," is first invoked, it loads several files
including Dink.dat, hard.dat, and map.dat.  These are created by DinkEdit.exe.
Then it loads and runs dink.ini (see the init() command), also mostly created
by DinkEdit.  Lastly, it runs two scripts: main.c, then start.c.

Main.c is about the same in every dmod with a single important exception: the
list of global variables.  There are at least two blocks of make_global_int()
statements in main.c: one defining required variables such as &magic which are
used by dink.exe itself, and an additional set or sets defining variables used
by scripts.  These vary widely from dmod to dmod.

Start.c is the last script the dink engine runs automatically.  It typically
does a number of housekeeping tasks, including displaying an opening screen
to the player.  Dink is initially depicted as a mouse pointer, and the opening
screen includes several sprites -- typically given the appearance of "buttons"
-- for the player to select with the mouse pointer (see also sp_brain(), brains
13 and 14).

The scripts attached to the buttons could be named in any way the dmod author
chooses, but so far as I know, this scheme is used invariably by convention:
  Start1.c is attached to the Start sprite/button.  It arranges to show the
           player any introductory cut scenes, changes Dink from a mouse
           pointer into a character, and sets &player_map as well as his X
           and Y location and sp_dir() to set his starting point in the game.
  Start2.c is attached to the Continue button.  It primarily does a load_game()
           to continue from some previous save.
  Start4.c basically allows players to change their mind about playing.  It
           does a kill_game().

(Originally, of course, there was also a Start3.c -- it was attached to an
"Order" button before Seth decided to distribute his game for free.  Whatever
his reasons for doing that, and whatever shortcomings the game might have, a
lot of us thank him...)

Whether the player chooses the Start1 or Start2 sprite, &player_map is set to
a value that tells the dink engine which screen to find in map.dat, effect a
load_screen() and draw_screen() on, and then run the scripts as Seth described
above: base script first, if one is defined; then the main() procedures of any
and all scripts attached to sprites on the screen.

There are seven general categories of scripts.  Three we have covered already:
  (1) game scripts -- Main.c and Start.c, run automatically by the dink engine.
      The other Start#.c scripts logically seem like members of this category,
      but if you want to get technical, they are type 3: sprite scripts.  There
      are at least two other game scripts, however: lraise.c, called when Dink
      gains a level; and dinfo.c, called when he dies.
  (2) base scripts -- attached to screens/maps, run before the screen is drawn,
      usually to set &vision.
  (3) sprite scripts -- attached to a sprite, either in DinkEdit or by an
      sp_script() command.  Their main() procedures are run when the screen is
      drawn; other procedures including touch(), talk(), push(), and hit(), are
      run when Dink performs some action on them.

The other four are:
  (4) item scripts -- these are attached to items in Dink's inventory.  As
      described in "Part 4: How Items Work", various procedures are run from
      these scripts when any event happens to the item, such as its being
      added, armed, or used.  This is the only type of script for which a
      main() procedure has no defined purpose.
  (5) key scripts -- this category includes Escape.c and Button6.c, run when
      the player presses the Esc or "M" key, respectively; as well as the newer
      "key-##" scripts which can be associated with most other keyboard keys.
  (6) independent scripts -- these are typically started by spawn() and run
      until they do a kill_this_task() or the player causes a load_game() to
      be performed.
  (7) utility scripts -- scripts such as make.c whose procedures are invoked
      by other scripts using the external() command.

Within limits of reasonable programming technique, of course, scripts can
change categories.  Most common is for a sprite script to become independent
using script_attach(1000).  Less often but still fairly common is for a sprite
script to also serve as a utility script to other, similar sprite scripts.



                       -= PART 7: KNOWN LIMITATIONS =-

Ok - try as we might, we can't be [deluded] into thinking DinkC is really C.
It is an [interpreted] scripting language written to understand C syntax.
To a DEGREE.  Here are the rules, pay attention or [and!] be prepared to
be frustrated!

*** Only do one command per line. ***

Instead of

  freeze(1); say("Hi",1); unfreeze(1);

do it like this:

  freeze(1);
  say("Hi",1);
  unfreeze(1);

This does not waste memory.

DinkC PARTLY supports multiple commands per line, but later I decided not
to pursue it -- it was eating up too much processor time with my slow
searches.

**** COOL MATH NOT SUPPORTED ****

DinkC can only understand ONE math question [or operation] at a time.

Dink understands: =, !=, +=, [-=], >, <, >=, <=, /, *

Instead of:

  if ( (5 + 3) > 2) ....

you must do something else such as:

  int &crap = 5;
  &crap += 3;
  if (&crap > 2)
  {
     //blahblah
  }

[Ok, after much frustration with this, here's the deal.  One of the first
dmod's I tinkered with had a healing spell that healed Dink just one point
per use, then you had to wait for it to recharge.  That was a help early in
the game, but since the Dink character was not allowed to increase his magic
when he gained a level, the spell became increasingly useless.  I decided that
the best fix was to modify the spell so that instead of giving just one healing
point every time, it would heal half the number of his level, rounded up.  In
other words, instead of "&life += 1;" I effectively wanted

  &life += ((&level + 1) / 2);

This is what I found out after a frustrating several hours:
(1) The ONLY addition and subtraction that can be done in DinkC is that which
    can be done with the += and -= operators.  "&a = &b + &c;" is NOT allowed.
    It must be done as
      &a = &b;
      &a += &c;
(2) Contrary to my initial assessment, the multiplication and division
    operators "/" and "*" are allowed, but they act like "*=" and "/=", even
    though they CANNOT be written that way.  So to simulate the (semi-) C
    statement "&life += ((&level + 1) / 2);" I needed to do this:
      int &healamt = &level;
      &healamt += 1;
      &healamt / 2;          // looks goofy, but acts like "&healamt /= 2;"
      &life += &healamt;     // adds 1 if level 1 or 2; 2 if 3 or 4; etc.
(3) There is a much more major bug in the DinkC interpreter than enforcing
    goofy syntax.  If the "/" operator is followed by more than ONE SINGLE
    SPACE, it acts like a kill_game() command!!!
      &healamt += 1;
      &healamt /  2;          // ends game immediately!!!
    [Tyrsis: "with math operators there must be only single spaces. Double
    (or no) spaces cause errors:
      &vvv  = 10; //  doesn't work,
      &vvv =  10; // works like...
      &vvv = 0;
      += and -= with double spaces (before and/or after operator) don't work."]

ManaUser reports that, even though the standard C operator /= is not supported,
the standard operator *= is supported after all.  I guess I didn't check that
one...]

You also cannot do &crap++; or &crap--.  Use &crap += 1; instead.

You can do:
Pap < Seth.  Grace Jones < Zorin.  Bond > OnnaTop.  K6 < p200mmx.
[It is not at all clear what Seth is trying to communicate.  Certainly the
above does not conform to DinkC syntax, not even if enclosed in parentheses
as part of an "if" statement.  My best guess is that he simply showing that
"if" can compare the values of two variables, e.g.
      if (&Pap < &Seth) ...
      if (&Grace_Jones < &Zorin) ...
      if (&Bond > &OnnaTop) ...
      if (&K6 < &p200mmx) ...
Your guess is no doubt as good as mine.]


*** BE CAREFUL WITH YOUR COMMENTS ***

Comment like this:

  int &crap = 5;
  //sets this to 5, duh

Not like this:
  
  int &crap = 5;    //sets this to 5, duh

This could cause a problem, some commands use 'does this char exist in this
string?' procedures.  Don't do it.  [I think he means his parser considers
a line at a time and searches the entire line for text.  If so, why doesn't
it internally truncate the line at a "//"?  That's what it's for!!!
   And anyway, Seth is not above violating this rule in his own code -- see
the entry for sp_nocontrol() in part 2.]

If a script command is not working, turn debug mode on and study the output
-- most likely you can figure what is going on from that.  If that fails, try
scripting it another way, or studying source that I have done in the dink/story
dir.  [actually, I have found few tools less useful than Dink's -debug mode.]

[ManaUser: "Same here. I almost never use it.  say() commands work better."]

Note:  The COMPLETE [original Dink] script source is zipped up in the DEVELOP
       dir.  (SOURCE.ZIP)


================================================================================
Appendix A: System Variables

  In the script MAIN.C for the original Dink Smallwood game, as released by
  RTSoft, the first block of 22 make_global_int() commands are prefaced with
  this comment: "These globals are REQUIRED by dink.exe (it directly uses
  them, removing any of these could result in a game crash."  They are
  defined in this order:

      &exp           &magic          &lifemax       &update_status
      &strength      &magic_level    &life          &missile_target
      &defense       &vision         &level         &enemy_sprite
      &cur_weapon    &result         &player_map    &magic_cost
      &cur_magic     &speed          &last_text     &missle_source
      &gold          &timing

  MAIN.C for "Mystery Island", also released by RTSoft, has a similar comment
  on its first block, but (1) it defines the variables in a different order,
  so apparently order does not matter; and (2) "&missle_source" is not
  defined, whatever that implies.

  MAIN.C for the "skeleton" dmod (Dan Walma's?) adds three variables to this
  list that are defined in both the original game and "Mystery Island", but
  not in the block defined as needed by dink.exe itself, and I don't they
  are quite as critical.  These three are:

      &save_x        &save_y         &dinklogo

  In the headers below:
    - The first line is the variable name preceded by a # to facilitate
      the use of a text editor's "find" function.
    - "Definition" is the make_global_int() command from main.c as released
      by RTSoft: if "Mystery Island" or the skeleton dmod's definition is
      different, there is an "(island)" or "(skeleton)" line.
    - "Description" is just a brief, one-line description.
    - "Referenced by" shows the primary readers of the variable's value.
      Certainly any variable may be read by a script, but "scripts" are not
      listed for some variables such as &defense because they do not represent
      the primary use of the variable.
    - "Modified by" lists the primary ways the variable's value gets changed.
      The dink engine will not allow some variables, such as &current_sprite,
      to be changed by a script.
    - "Valid values" shows the acceptable or typical range of values for this
      variable.  All are 32-bit signed integers, but few may legitimately be
      zero or negative.  For the ones referenced by the dink engine, such
      values can cause strange behavior, even crashes.

#&cur_magic
# Definition:    make_global_int("&cur_magic", 0);
# Description:   Slot # (1-8) of Dink's currently armed magic; 0 if none
# Referenced by: arm_magic()
# Modified   by: Inventory screen; scripts
# Valid values:  0 to 8

  This variable is set to the slot number (1-8) of the magic armed by the
  player using the Inventory screen.  It can also be set to 0-8 for use with
  arm_magic() (see).


#&cur_weapon
# Definition:    make_global_int("&cur_weapon", 0);
# Description:   Slot # (1-16) of Dink's currently armed weapon
# Referenced by: arm_weapon()
# Modified   by: Inventory screen; scripts
# Valid values:  1 to 16

  This variable is set to the slot number (1-16) of the weapon or item armed
  by the player using the Inventory screen.  It can also be set to 1-16 for
  use with arm_weapon() (see).  Setting it to 0 and calling arm_weapon() to
  disarm all weapons is NOT recommended.


#&current_script
# Definition:    [internal, not defined by a make_global_int()]
# Description:   This script's own script number
# Referenced by: scripts (see narrative)
# Modified   by: dink engine (may not be modified by script)
# Valid values:  1 to 200 (rarely over 50 or so)

  Any script will see its own script number in this undocumented variable.
  If the script was invoked by spawn() or sp_script(), it is the same value
  as would have been returned by those commands.  The only known reason for
  a script needing to know its own script number would be to store it in an
  external variable so its contained procs could be invoked by other scripts
  using run_script_by_number().  There is no particular advantage to a
  script running its own procs that way: it is about the same as doing a
  goto, except the goto cannot reference the proc name itself and requires
  a label to be defined.


#&current_sprite
# Definition:    [internal, not defined by a make_global_int()]
# Description:   Sprite number to which a script is attached
# Referenced by: sprite scripts
# Modified   by: dink engine (not directly modifiable by script)
# Valid values:  1 to 300? (rarely over 100)

  This variable is set by the dink engine to the sprite number to which the
  script is attached.  For scripts such as item scripts and key scripts, the
  value of this variable seems to be 1 (Dink's sprite number, even though
  scripts are not allowed to be attached to Dink).  Script_attach() will
  immediately change the value of &current_sprite.


#&defense
# Definition:    make_global_int("&defense", 0);
# Description:   Dink's defense in battle
# Referenced by: dink engine
# Modified   by: scripts (notably lraise and bpotion)
# Valid values:  0 up (typically 0 to 50 or so)

  Counterpart of sp_defense() attribute for Dink.  This number is subtracted
  from any hit taken by Dink before the hit is subtracted from &health
  (in general, however, if &defense is >= hits, Dink still has a 50% chance
  of taking one hit.)  The dink engine will crash if &defense is ever less
  than zero.

  &Defense is typically increased by lraise.c, called when Dink's level
  raises; and by bpotion.c and apotion.c, the scripts for the blue defense
  potion and multi-colored "megapotion" bottles.  But it may in fact may be
  modified by any script in any way that makes sense to the dmod author, as
  long as its value remains in a valid range.


#&dinklogo
# Definition:    make_global_int("&dinklogo", 0);
# Description:   Sprite number for main Dink Smallwood logo on start screen
# Referenced by: start-3.c ("purchase" button script)
# Modified   by: start.c
# Valid values:  probably always in the range 2 to 5


#&enemy_sprite
# Definition:    make_global_int("&enemy_sprite", 0);
# Description:   Sprite number of hitting sprite
# Referenced by: scripts (notably hit() procs of monster-brain scripts)
# Modified   by: dink engine
# Valid values:  1 to 300?

  When a sprite takes a hit, the dink engine sets &enemy_sprite to the
  sprite number of the hitting sprite before invoking the attacked sprite's
  hit() procedure.

  Question still to be answered: if Dink shoots a fireball at an enemy, is
  &enemy_sprite set to 1 (Dink's sprite number), the missile's sprite number,
  left unchanged, or what?


#&exp
# Definition:    make_global_int("&exp", 0);
#   (island):    make_global_int("&exp", 500 );
# Description:   Dink's experience points
# Referenced by: dink engine (to know when to call lraise.c)
# Modified   by: dink engine; scripts
# Valid values:  0 up (typically 5 or fewer digits)

  This is the amount of experience Dink has accumulated toward his next
  level.  The script lraise.c will be called when it exceeds the value
  "&level * &level * 100".

  See &level for more detail.


#&gold
# Definition:    make_global_int("&gold", 0);
#   (island):    make_global_int("&gold", 500);
# Description:   Dink's money, his number of gold pieces
# Referenced by: dink engine (displayed in status area), scripts
# Modified   by: scripts
# Valid values:  0 up (typically 5 or fewer digits)


#&last_text
# Definition:    make_global_int("&last_text", 0);
# Description:   Sprite number of last text sprite displayed
# Referenced by: scripts
# Modified   by: say() commands, including say_stop() say_xy(), etc.
# Valid values:  1 to 300?


#&level
# Definition:    make_global_int("&level", 1);
#   (island):    make_global_int("&level", 5);
# Description:   Dink's player level, typically 1 to 20 or so.
# Referenced by: dink engine, scripts
# Modified   by: lraise.c; optionally any script.
# Valid values:  1 to 32 enforced by stock lraise.c; higher values not tested

  The dink engine uses the value of &level to decide how many experience
  points Dink must accumulate in &exp before lraise.c is called again.
  This is effectively the logic used each time &exp is modified:

     int &threshhold = &level;
     &threshhold *= &threshhold; // compute square of &level
     &threshhold *= 100;         // multiply square by 100
     if (&threshhold > &exp)
     {
       external("lraise","main");
       &exp -= &threshhold;
     }

  In other words, the dink engine relies on lraise.c to change &level but not
  to reset &exp.  Among other things, this means that Dink may be granted a
  bonus level simply by calling lraise.c [external("lraise","main")]; &exp
  will not be automatically modified in this case.

  Also note that if the dmod grants Dink so many points that multiple levels
  are indicated, lraise.c is still only called no more than once per time
  &exp is changed.  Example: &level is currently 3, &exp is currently &750,
  and then the dmod grants Dink 2000 experience points for a single deed.
  This is what happens:

     - &exp is changed by the script to 2750
     - &threshhold is computed as 3*3*100 or 900
     - lraise.c is called, presumably setting &level to 4
     - the dink engine reduces &exp by 900 to 1850.

  Now the game goes on.  Dink must do something else to raise his &exp before
  the engine will notice that 1850 is still above the new implied threshhold
  of 1600 (4*4*100) and call lraise.c again.


#&life
# Definition:    make_global_int("&life", 10);
#   (island):    make_global_int("&life", 50);
# Description:   Dink's remaining hitpoints
# Referenced by: dink engine, scripts
# Modified   by: dink engine, scripts
# Valid values:  1 to &lifemax

  &life is automatically reduced by the touch_damage of any sprite Dink
  touches or a random number based on the strength of any sprite that attacks
  him, subject to certain adjustments: (1) the amount of the reduction is
  itself reduced by Dink's &defense; and (2) if that would reduce the hit
  or damage to zero or below, there is still a 50-50 chance he will take a
  one-point hit.

  If &life reaches zero or below, Dink is determined to have died.  Dink's
  die() procedure is found in dinfo.c.

  &life, along with &lifemax, is graphically displayed in the status area.

  &life is never automatically increased by the dink engine and must be
  modified by script.  Here is typical code, based on sheart.c ("small
  heart" script) as released by RTSoft:

    void touch( void )
    {
      &life += 3;
      if (&life > &lifemax)
        &life = &lifemax;
      ...
    }

  A question still to be answered is, what are the implications if &life
  were allowed to exceed &lifemax?  As I recall, this only makes the status
  graph look a little strange, but it should be researched further.


#&lifemax
# Definition:    make_global_int("&lifemax", 10);
#   (island):    make_global_int("&lifemax", 50);
# Description:   Dink's hitpoints when he is fully healed
# Referenced by: dink engine (graphed in status area); scripts
# Modified   by: scripts
# Valid values:  1 up (status area cannot graph values higher than about
                       220, but such values have no other adverse affect)

  &Lifemax is typically increased by lraise.c, called when Dink's level
  raises; and by gheart.c, the script for a gold heart.  But it may in fact
  be modified by any script in any way that makes sense to the dmod author,
  as long as its value remains in a valid range.

  See also &life, above.


#&magic
# Definition:    make_global_int("&magic", 0);
#   (island):    make_global_int("&magic", 20);
# Description:   Dink's magic level
# Referenced by: dink engine (shown in status area)
# Modified   by: scripts
# Valid values:  0 up (typically 0 to 50 or so)

  This all needs to be researched and verified, but I think &magic,
  &magic_cost, and &magic_level interact about as follows:
    - The arm() proc for a magic item sets &magic_cost to an amount of magic
      deemed by the dmod author to be necessary to use that particular item.
    - If &magic_level is less than &magic_cost, the dink engine periodically
      adds &magic to &magic_level.  This happens several times a second but
      the exact frequency is unknown (to me, anyway).
    - If Dink has a magic-item armed, and the Shift key is pressed, and
      &magic_level equals &magic_cost, the use() procedure in the item's
      attached script is invoked.  That proc typically resets &magic_level
      to zero, along with whatever else it does (shoots a fireball or
      whatever).

  In the status area, &magic is displayed directly, while &magic_cost and
  &magic_level are displayed graphically.  The box around the armed magic
  graphic is divided (internally, not visually) into the number of sections
  specified by &magic_cost, and the number of those sections represented by
  &magic_level's current value are colored in.

  &Magic is typically increased by lraise.c, called when Dink's level
  raises; and by ppotion.c and apotion.c, the scripts for the purple magic
  potion and multi-colored "megapotion" bottles.  But it may in fact may be
  modified by any script in any way that makes sense to the dmod author, as
  long as its value remains in a valid range.


#&magic_cost
# Definition:    make_global_int("&magic_cost", 0);
# Description:   Amount of magic required to use a magic item
# Referenced by: dink engine
# Modified   by: arm() and disarm() procs of magic-item scripts
# Valid values:  0 up (typically 0 to 2000 or so)

  (see &magic, above)


#&magic_level
# Definition:    make_global_int("&magic_level", 0);
# Description:   Amount of magic currently accumulated by Dink
# Referenced by: dink engine
# Modified   by: dink engine, use() proc of magic-item scripts
# Valid values:  0 to &magic_cost

  (see &magic, above)


#&missile_target
# Definition:    make_global_int("&missile_target", 0);
# Description:   Sprite number of sprite hit by a missile
# Referenced by: damage() proc of missile scripts
# Modified   by: dink engine
# Valid values:  1 to 300?

  When a missle (brain 11 or 17 sprite) hits something, the dink engine
  sets &missile_target to the sprite number of the sprite it hit, then
  calls the damage() proc of the missile's script.


#&missle_source
# Definition:    make_global_int("&missle_source", 0);
#   (island):    [not defined or referenced in "Mystery Island"]
# Description:   Sprite number of an attacking missile
# Referenced by: hit() proc of sprite scripts
# Modified   by: dink engine
# Valid values:  1 to 300?

  Note the misspelling of "missile" in this variable name; as always, the
  variable name must be (mis)spelled the same way to be referenced.

  When a sprite is hit by a missile (brain 11 or 17 sprite), the dink engine
  sets &missle_source to the missile's sprite number before calling the hit()
  procedure of the damaged sprite's script.


#&player_map
# Definition:    make_global_int("&player_map", 1);
#  (skeleton):   make_global_int("&player_map", 0);
# Description:   Screen number (1-768) of Dink's current location
# Referenced by: dink engine, scripts, load_screen() command
# Modified   by: dink engine, scripts
# Valid values:  1 to 768

  The world according to the dink engine is a grid of screens 32 columns in
  the east-west direction by 24 rows in the north-south direction.  Each
  screen is termed a "map" in DinkEdit, hence the somewhat confusing name
  for this variable.  Each of these screens or maps are numbered sequentially:
  map 1 is the northwest corner, map 2 is the next screen east, and so forth
  to map 32; then map 33 is the screen south of map 1, and the sequence
  continues west to east and then north to south: the screen in the southeast
  corner is number 768.

  Dink's starting screen is typically established by setting &player_map in
  script start-1.c (though in the original game, Dink starts in map 1, the
  initial value of &player_map as shown above, and so start-1.c does not need
  to change it).  This is similar to a scripted warp: the heart of such a
  procedure sets &player_map as well as Dink's (X,Y) to his destination
  location, then uses the load_screen() and draw_screen() commands.
  Load_screen() loads the screen specified by &player_map.

  The other way &player_map gets modified is automatically, by the dink
  engine, during a screen scroll.  If Dink walks off the east edge of the
  current screen, the engine adds 1 to &player_map; if he walks off the west
  edge, the engine subtracts 1; if he walks off the north or south edges, 32
  is subtracted from or added to &player_map.  In all cases, if the result
  would be less than 1 or greater than 768, &player_map is not changed and
  Dink is not allowed to exit the screen in that direction.

  This processing offers a couple of possibilities that may not be obvious
  at first glance.  Tyrsis pointed one of them out: Dink can be permitted to
  go east from a screen on the east edge of the dink world.  In such a case,
  the scroll will take him to the west edge of the next row south. Same effect
  if Dink can go west from a screen on the west edge.  This is ordinarily
  prevented in all dmods by blocking Dink's way with hardness, but could be
  an interesting and perfectly valid twist in an underground cave, for example.

  The other possibility involves having a script change &player_map before
  a screen scroll.  Remember, if Dink walks off the east edge of the screen,
  he is not scrolled to the next screen east so much as to screen found by
  adding 1 to &player_map.  If &player_map no longer contains the number of
  the current screen, the screen scroll could be to anywhere.  A clever
  scripter could use this to implement some very sneaky warps.


#&result
# Definition:    make_global_int("&result", 0);
# Description:   Return value from Choice() construct and wait_for_button()
# Referenced by: scripts
# Modified   by: choice() construct, wait_for_button() command (or directly)
# Valid values:  any (though choice() will only set 1 to about 20)

  &Result may be used in any way desired, but its intended purpose is to
  return the number of the player's selection from a choice() menu, or a
  number representing a key pressed while a wait_for_button() command is
  active.  See wait_for_button() command and "Part 5: How the Choice Statement
  Works" for full details.


#&save_x
#&save_y
# Definition:    make_global_int("&save_x", 0);
#                make_global_int("&save_y", 0);
# Description:   Killed monster's X/Y location
# Referenced by: utility scripts make.c & emake.c
# Modified   by: die() procs in monster scripts
# Valid values:  &save_X: 0 to 639; &save_Y: 0 to 399


#&speed
# Definition:    make_global_int("&speed", 1);
# Description:   unknown
# Referenced by: unknown
# Modified   by: unknown
# Valid values:  unknown


#&strength
# Definition:    make_global_int("&strength", 3);
#   (island):    make_global_int("&strength", 25);
#  (skeleton):   make_global_int("&strength", 1);
# Description:   Dink's strength attribute
# Referenced by: dink engine
# Modified   by: sprite scripts and weapon-item scripts
# Valid values:  1 up (typically 1 to 100 or so)

  &Strength is displayed in the status area.  When Dink hits something with
  his fist or a true melee weapon such as a sword (as opposed to a missle
  like the flying axe), the monster takes a number of hits computed by
  randomly selecting a number in the upper half of Dink's &strength range
  and subtracting the monster's sp_defense() attribute.  For example, if
  &strength is 20 and the monster's defense is 5, the hit will be for a
  random number 11 to 20 hits minus the 5 defense, or 6 to 15 hits.

  &Strength is typically increased by lraise.c, called when Dink's level
  raises; and by rpotion.c and apotion.c, the scripts for the red strength
  potion and multi-colored "megapotion" bottles.  It is also increased
  temporarily by weapon scripts while Dink has the weapon armed.  But it
  may in fact may be modified by any script in any way that makes sense to
  the dmod author, as long as its value remains in a valid range.


#&timing
# Definition:    make_global_int("&timing", 0);
# Description:   unknown
# Referenced by: unknown
# Modified   by: unknown
# Valid values:  unknown


#&update_status
# Definition:    make_global_int("&update_status", 0);
# Description:   Control whether status area is updated
# Referenced by: dink engine
# Modified   by: scripts (also dink engine?)
# Valid values:  0 or 1?

  ManaUser: "The statement '&update_status = 0;' will stop the game from
  updating the life and magic bars on the status area. I'm not sure of its
  effect on any of the other information shown there."


#&vision
# Definition:    make_global_int("&vision", 0);
# Description:   Control display of additional sprites
# Referenced by: dink engine, scripts
# Modified   by: dink engine, "base" scripts.
# Valid values:  0 up (0 to about 8 recommended)

  To DinkEdit, a "vision" is a set of one or more sprites associated with
  a vision number other than zero(0).  Sprites associated with vision 0 in
  DinkEdit are always displayed by dink.exe (subject to the rules of
  invisibility and so forth as discussed elsewhere), but sprites associated
  with any other vision are only displayed if &vision is exactly equal to
  that number.  For example, a screen with a river might have a broken bridge
  in vision 1 and a functional bridge in vision 2.  Then the base script can
  choose whether to show the broken bridge or the repaired bridge (or neither),
  but not both.  All sprites to be shown regardless of the bridge's state of
  repair would be vision 0.

  (Any number, at least any positive number, which can be contained in a
  32-bit signed integer are valid vision numbers.  Some dmod authors have
  used, and later regretted using, numbers like 8996789 and 5190633 for
  vision numbers.  Recommended practice is to keep the numbers in single
  digits if at all possible, it makes debugging and maintenance of the dmod
  much easier.)

  In dink.exe, every time Dink moves to a new screen, &vision is set to 0,
  then the main() proc of any "base script" associated with that screen is
  run.  If that script changes &vision before doing a wait() or some other
  command that causes the script to pause or end, then the sprites associated
  with that vision will be drawn along with the vision 0 sprites.

  Despite the existence of command force_vision(), and despite numerous
  creative attempts by several dmod authors, no other way has been found
  to change vision in dink.exe.  If a screen does not a base script, then
  only vision 0 sprites can be displayed.


================================================================================
Appendix B: warp-eg.c

   This is a sample script that might be assigned to a warping sprite
   created by the create_sprite() command (see):

   void main ( void )
   {
         // temporarily disable the touch() procedure[*]:
     sp_touch_damage(&current_sprite, 0);
         // set brain and pframe in case they weren't set by create_sprite():
     sp_brain(&current_sprite, 6);
     sp_pframe(&current_sprite, 1);
         // override create_sprite's pseq; start the warp sprite's animation:
     sp_seq(&current_sprite, 170);
         // set the animation rate to about 30 frames a second:
         // [except, shouldn't this be sp_frame_delay()?]
     sp_timing(&current_sprite, 33);
         // activate the touch() procedure after a second[*]:
     wait(1000);
     sp_touch_damage(&current_sprite, -1);
   }

   void touch ( void )
   {
         // prevent more instances of touch() from starting:
     sp_touch_damage(&current_sprite, 0);
         // freeze Dink until the new screen is loaded:
     freeze(1);
         // keep script active after current screen and sprites are gone:
     script_attach(1000);
         // optional pause for effect:
     wait(500);
         // another optional effect - if not using, use sp_nodraw(1,1) instead:
     fade_down();
         // screen to warp to, 1 to 768:
     &player_map = 768;
         // Dink's X,Y coordinates on the new screen:
     sp_x(1, 320);
     sp_y(1, 200);
         // face Dink in an appropriate direction, usually toward the player:
     sp_dir(1, 2);
         // load screen specified by &player_map:
     load_screen();
         // start music if desired:
     playmidi("5.mid");
         // draw the screen's tiles and sprites:
     draw_screen();
         // if sp_noclip() is in use in this dmod, redraw the status area:
     draw_status();
         // undo fade_down(), or do a sp_nodraw(1,0) to undo a sp_nodraw(1,1):
     fade_up();
         // kill script manually, since we did a script_attach(1000) earlier:
     kill_this_task();
         // note that no unfreeze(1) is required.
   }   

[*] The delay in main() before setting touch damage to -1 solves a subtle
    problem: if the sprite was created in a monster's die() procedure, and
    if the monster was killed by hellfire magic, that spell seems to be able
    to trigger the touch() procedure in this script.  By introducing a short
    delay, we give the fire time to move on before touch() can be triggered.
    Dink could also be right on top of the monster when it dies, and the
    delay allows the player a moment to see what happened before the warp
    takes effect.

ManaUser: "I'm not sure that's exactly the problem. But there definitely IS
    a problem of some kind with touch() and newly created sprites.  It may be
    even wider in scope."

    Yes, I tend to agree.  Since I wrote the above, I had a problem where the
    newly-created sprite's touch() was invoked as soon as its main() did the
    sp_touch_damage(-1) thing, even if I did a wait(5000) first and insured
    that there was absolutely NO action going on.  But then I rearranged the
    order of statements in the monster's die() procedure, where the sprite was
    being created, and no wait() at all was required.

    It is my current belief that this bug happens if create_sprite() includes
    0 for the <brain> argument, and then sp_brain() is used to set the brain
    to 6 (repeating sequence).  I picked this technique up from somewhere but
    I now recommend against it.  Specify the desired brain in create_sprite()
    and touch() is not activated until Dink really touches the sprite.

    I was hoping this bug was not shared by buttonon(), but it is.]

************************* (end of document DinkC.txt) **************************
